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;
|
||||
activeNotes++;
|
||||
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
|
||||
{
|
||||
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
|
||||
insertEventInVector(cc[ccNumber], delay, ccValue);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,13 @@ public:
|
|||
const EventVector& getPitchEvents() 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:
|
||||
|
||||
/**
|
||||
|
|
@ -243,6 +250,7 @@ private:
|
|||
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
float alternate { 0.0f };
|
||||
unsigned internalClock { 0 };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& l
|
|||
{
|
||||
ASSERT(events.size() > 0);
|
||||
ASSERT(events[0].delay == 0);
|
||||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
|
||||
|
|
@ -103,7 +104,11 @@ void linearEnvelope(const EventVector& events, absl::Span<float> envelope, F&& l
|
|||
{
|
||||
ASSERT(events.size() > 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)
|
||||
return;
|
||||
|
|
@ -155,6 +160,7 @@ void multiplicativeEnvelope(const EventVector& events, absl::Span<float> envelop
|
|||
|
||||
if (envelope.size() == 0)
|
||||
return;
|
||||
|
||||
const auto maxDelay = static_cast<int>(envelope.size() - 1);
|
||||
|
||||
auto lastValue = lambda(events[0].value);
|
||||
|
|
|
|||
|
|
@ -1526,7 +1526,21 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
|
|||
assert(false);
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ Synth::Impl::Impl()
|
|||
resetVoices(config::numVoices);
|
||||
|
||||
// modulation sources
|
||||
genController_.reset(new ControllerSource(resources_));
|
||||
genController_.reset(new ControllerSource(resources_, voiceManager_));
|
||||
genLFO_.reset(new LFOSource(voiceManager_));
|
||||
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
||||
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
|
||||
|
|
@ -1314,6 +1314,8 @@ void Synth::pitchWheel(int delay, int pitch) noexcept
|
|||
for (auto& voice : impl.voiceManager_) {
|
||||
voice.registerPitchWheel(delay, normalizedPitch);
|
||||
}
|
||||
|
||||
impl.performHdcc(delay, ExtendedCCs::pitchBend, normalizedPitch, false);
|
||||
}
|
||||
|
||||
void Synth::aftertouch(int delay, uint8_t aftertouch) noexcept
|
||||
|
|
@ -1704,6 +1706,7 @@ void Synth::Impl::setupModMatrix()
|
|||
|
||||
switch (sourceKey.id()) {
|
||||
case ModId::Controller:
|
||||
case ModId::PerVoiceController:
|
||||
gen = genController_.get();
|
||||
break;
|
||||
case ModId::AmpLFO:
|
||||
|
|
|
|||
|
|
@ -196,6 +196,12 @@ struct Voice::Impl
|
|||
*/
|
||||
void off(int delay, bool fast = false) noexcept;
|
||||
|
||||
/**
|
||||
* @brief Setup the extended CC values for the voice
|
||||
*
|
||||
*/
|
||||
void updateExtendedCCValues() noexcept;
|
||||
|
||||
const NumericId<Voice> id_;
|
||||
StateListener* stateListener_ = nullptr;
|
||||
|
||||
|
|
@ -288,6 +294,10 @@ struct Voice::Impl
|
|||
|
||||
bool followPower_ { false };
|
||||
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)
|
||||
|
|
@ -363,6 +373,20 @@ Voice::Impl::Impl(int voiceNumber, Resources& resources)
|
|||
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
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -384,6 +408,8 @@ bool Voice::startVoice(Layer* layer, int delay, const TriggerEvent& event) noexc
|
|||
|
||||
impl.switchState(State::playing);
|
||||
|
||||
impl.updateExtendedCCValues();
|
||||
|
||||
ASSERT(delay >= 0);
|
||||
if (delay < 0)
|
||||
delay = 0;
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@ enum InterpolatorModel : int;
|
|||
class LFO;
|
||||
class FlexEnvelope;
|
||||
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
|
||||
* and tasked to play a given region until the end, stopping on note-offs, off-groups
|
||||
|
|
@ -384,6 +392,13 @@ public:
|
|||
*/
|
||||
const TriggerEvent& getTriggerEvent();
|
||||
|
||||
/**
|
||||
* @brief Get the extended CC values
|
||||
*
|
||||
* @return const ExtendedCCValues&
|
||||
*/
|
||||
const ExtendedCCValues& getExtendedCCValues() const noexcept;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Check if the voice already belongs to a sister ring
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ int ModIds::flags(ModId id) noexcept
|
|||
return kModIsPerVoice;
|
||||
case ModId::ChannelAftertouch:
|
||||
return kModIsPerCycle;
|
||||
case ModId::PerVoiceController:
|
||||
return kModIsPerVoice;
|
||||
|
||||
// targets
|
||||
case ModId::MasterAmplitude:
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ enum class ModId : int {
|
|||
PitchEG,
|
||||
FilEG,
|
||||
ChannelAftertouch,
|
||||
|
||||
PerVoiceController,
|
||||
_SourcesEnd,
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -113,6 +113,10 @@ std::string ModKey::toString() const
|
|||
return absl::StrCat("FilterEG {", region_.number(), "}");
|
||||
case ModId::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:
|
||||
return absl::StrCat("MasterAmplitude {", region_.number(), "}");
|
||||
|
|
|
|||
|
|
@ -19,13 +19,15 @@ struct ControllerSource::Impl {
|
|||
float getLastTransformedValue(uint16_t cc, uint8_t curve) const noexcept;
|
||||
double sampleRate_ = config::defaultSampleRate;
|
||||
Resources* res_ = nullptr;
|
||||
VoiceManager* voiceManager_ = nullptr;
|
||||
absl::flat_hash_map<ModKey, Smoother> smoother_;
|
||||
};
|
||||
|
||||
ControllerSource::ControllerSource(Resources& res)
|
||||
ControllerSource::ControllerSource(Resources& res, VoiceManager& manager)
|
||||
: impl_(new Impl)
|
||||
{
|
||||
impl_->res_ = &res;
|
||||
impl_->voiceManager_ = &manager;
|
||||
}
|
||||
|
||||
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)voiceId;
|
||||
|
||||
const ModKey::Parameters p = sourceKey.parameters();
|
||||
const Resources& res = *impl_->res_;
|
||||
const Curve& curve = res.curves.getCurve(p.curve);
|
||||
const MidiState& ms = res.midiState;
|
||||
const EventVector& events = ms.getCCEvents(p.cc);
|
||||
bool canShortcut = false;
|
||||
|
||||
auto transformValue = [p, &curve](float x) {
|
||||
return curve.evalNormalized(x);
|
||||
};
|
||||
|
||||
if (p.step > 0.0f)
|
||||
linearEnvelope(events, buffer, transformValue, p.step);
|
||||
else
|
||||
linearEnvelope(events, buffer, transformValue);
|
||||
// Ignore the eventual curve for the extended CCs
|
||||
auto quantize = [p](float x) {
|
||||
if (p.step > 0.0f)
|
||||
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);
|
||||
if (it != impl_->smoother_.end()) {
|
||||
Smoother& s = it->second;
|
||||
bool canShortcut = events.size() == 1;
|
||||
s.process(buffer, buffer, canShortcut);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#pragma once
|
||||
#include "../ModGenerator.h"
|
||||
#include "../../VoiceManager.h"
|
||||
#include <memory>
|
||||
|
||||
namespace sfz {
|
||||
|
|
@ -14,7 +15,7 @@ struct Resources;
|
|||
|
||||
class ControllerSource : public ModGenerator {
|
||||
public:
|
||||
explicit ControllerSource(Resources& res);
|
||||
explicit ControllerSource(Resources& res, VoiceManager& manager);
|
||||
~ControllerSource();
|
||||
void setSampleRate(double sampleRate) override;
|
||||
void setSamplesPerBlock(unsigned count) override;
|
||||
|
|
|
|||
|
|
@ -410,6 +410,36 @@ TEST_CASE("[Modulations] EG v1 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;
|
||||
synth.loadSfzString("/modulation.sfz", R"(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue