Skip to content

Commit

Permalink
Support the extended CCs of SFZv2 except poly aftertouch
Browse files Browse the repository at this point in the history
Poly aftertouch can be added in the future in a similar way
  • Loading branch information
paulfd committed Mar 26, 2021
1 parent 9386ed2 commit 6cbeefc
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/sfizz/MidiState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ void sfz::MidiState::noteOnEvent(int delay, int noteNumber, float velocity) noex
noteOnTimes[noteNumber] = internalClock + static_cast<unsigned>(delay);
lastNotePlayed = noteNumber;
activeNotes++;
alternate = alternate == 0.0f ? 1.0f : 0.0f;
}

}
Expand Down
8 changes: 8 additions & 0 deletions src/sfizz/MidiState.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ class MidiState
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:

/**
Expand Down Expand Up @@ -227,6 +234,7 @@ class MidiState

float sampleRate { config::defaultSampleRate };
int samplesPerBlock { config::defaultSamplesPerBlock };
float alternate { 0.0f };
unsigned internalClock { 0 };
};
}
16 changes: 15 additions & 1 deletion src/sfizz/Region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1486,7 +1486,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;
Expand Down
5 changes: 4 additions & 1 deletion src/sfizz/Synth.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -1265,6 +1265,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
Expand Down Expand Up @@ -1640,6 +1642,7 @@ void Synth::Impl::setupModMatrix()

switch (sourceKey.id()) {
case ModId::Controller:
case ModId::PerVoiceController:
gen = genController_.get();
break;
case ModId::AmpLFO:
Expand Down
26 changes: 26 additions & 0 deletions src/sfizz/Voice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,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;

Expand Down Expand Up @@ -283,6 +289,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)
Expand Down Expand Up @@ -358,6 +368,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(Region* region, int delay, const TriggerEvent& event) noexcept
{
Impl& impl = *impl_;
Expand All @@ -376,6 +400,8 @@ bool Voice::startVoice(Region* region, int delay, const TriggerEvent& event) noe

impl.switchState(State::playing);

impl.updateExtendedCCValues();

ASSERT(delay >= 0);
if (delay < 0)
delay = 0;
Expand Down
15 changes: 15 additions & 0 deletions src/sfizz/Voice.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ namespace sfz {
enum InterpolatorModel : int;
class LFO;
class FlexEnvelope;

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
Expand Down Expand Up @@ -383,6 +391,13 @@ class Voice {
*/
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
Expand Down
2 changes: 2 additions & 0 deletions src/sfizz/modulations/ModId.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion src/sfizz/modulations/ModId.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ enum class ModId : int {
PitchEG,
FilEG,
ChannelAftertouch,

PerVoiceController,
_SourcesEnd,

//--------------------------------------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions src/sfizz/modulations/ModKey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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(), "}");
Expand Down
100 changes: 90 additions & 10 deletions src/sfizz/modulations/sources/Controller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -85,27 +87,105 @@ 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);
// Otherwise it clamps the bipolar values
if (p.curve != 0)
x = curve.evalNormalized(x);

return x;
};

auto extendedCCTransform = [p, &curve](float x) {
// Otherwise it clamps the bipolar values
if (p.curve != 0)
x = curve.evalNormalized(x);

if (p.step > 0.0f)
return std::trunc(x / p.step) * p.step;

return x;
};

if (p.step > 0.0f)
linearEnvelope(events, buffer, transformValue, p.step);
else
linearEnvelope(events, buffer, transformValue);
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, extendedCCTransform(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, extendedCCTransform(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, extendedCCTransform(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, extendedCCTransform(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, extendedCCTransform(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, extendedCCTransform(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, extendedCCTransform(fillValue));
canShortcut = true;
break;
}
default: {
const EventVector& events = ms.getCCEvents(p.cc);
if (p.step > 0.0f)
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);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/sfizz/modulations/sources/Controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#pragma once
#include "../ModGenerator.h"
#include "../../VoiceManager.h"
#include <memory>

namespace sfz {
Expand All @@ -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;
Expand Down
30 changes: 30 additions & 0 deletions tests/ModulationsT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -408,3 +408,33 @@ TEST_CASE("[Modulations] EG v1 CC connections")
R"("FilterEG {1}" -> "FilterCutoff {1, N=1}")",
}, 2));
}

TEST_CASE("[Modulations] Extended CCs 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));
}

0 comments on commit 6cbeefc

Please sign in to comment.