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:
Paul Fd 2021-03-25 12:56:13 +01:00
parent 40d18f0e23
commit b3a73814d5
13 changed files with 196 additions and 15 deletions

View file

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

View file

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

View file

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

View file

@ -1526,7 +1526,21 @@ bool sfz::Region::processGenericCc(const Opcode& opcode, OpcodeSpec<float> spec,
assert(false);
break;
}
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;

View file

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

View file

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

View file

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

View file

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

View file

@ -30,7 +30,7 @@ enum class ModId : int {
PitchEG,
FilEG,
ChannelAftertouch,
PerVoiceController,
_SourcesEnd,
//--------------------------------------------------------------------------

View file

@ -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(), "}");

View file

@ -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);
};
// 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);
else
linearEnvelope(events, buffer, transformValue);
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);
}
}

View file

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

View file

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