Preliminary work for aftertouch handling

- Add the aftertouch modulation source
- Dispatch aftertouch events and track the aftertouch status in the midi state
- Parse cutoff_chanaft
- Convert aftertouch messages to an extended CC message
This commit is contained in:
Paul Fd 2021-02-05 00:39:07 +01:00 committed by Paul Ferrand
parent 02c5de1237
commit 6deb3d37d7
19 changed files with 249 additions and 47 deletions

View file

@ -78,34 +78,30 @@ int process(jack_nframes_t numFrames, void* arg)
switch (midi::status(event.buffer[0])) { switch (midi::status(event.buffer[0])) {
case midi::noteOff: noteoff: case midi::noteOff: noteoff:
// DBG("[MIDI] Note " << +event.buffer[1] << " OFF at time " << event.time);
synth->noteOff(event.time, event.buffer[1], event.buffer[2]); synth->noteOff(event.time, event.buffer[1], event.buffer[2]);
break; break;
case midi::noteOn: case midi::noteOn:
if (event.buffer[2] == 0) if (event.buffer[2] == 0)
goto noteoff; goto noteoff;
// DBG("[MIDI] Note " << +event.buffer[1] << " ON at time " << event.time);
synth->noteOn(event.time, event.buffer[1], event.buffer[2]); synth->noteOn(event.time, event.buffer[1], event.buffer[2]);
break; break;
case midi::polyphonicPressure: case midi::polyphonicPressure:
// DBG("[MIDI] Polyphonic pressure on at time " << event.time); // Not implemented
break; break;
case midi::controlChange: case midi::controlChange:
// DBG("[MIDI] CC " << +event.buffer[1] << " at time " << event.time);
synth->cc(event.time, event.buffer[1], event.buffer[2]); synth->cc(event.time, event.buffer[1], event.buffer[2]);
break; break;
case midi::programChange: case midi::programChange:
// DBG("[MIDI] Program change at time " << event.time); // Not implemented
break; break;
case midi::channelPressure: case midi::channelPressure:
// DBG("[MIDI] Channel pressure at time " << event.time); synth->aftertouch(event.time, event.buffer[1]);
break; break;
case midi::pitchBend: case midi::pitchBend:
synth->pitchWheel(event.time, midi::buildAndCenterPitch(event.buffer[1], event.buffer[2])); synth->pitchWheel(event.time, midi::buildAndCenterPitch(event.buffer[1], event.buffer[2]));
// DBG("[MIDI] Pitch bend at time " << event.time);
break; break;
case midi::systemMessage: case midi::systemMessage:
// DBG("[MIDI] System message at time " << event.time); // Not implemented
break; break;
} }
} }

View file

@ -57,6 +57,7 @@ SFIZZ_SOURCES = \
src/sfizz/modulations/ModKeyHash.cpp \ src/sfizz/modulations/ModKeyHash.cpp \
src/sfizz/modulations/ModMatrix.cpp \ src/sfizz/modulations/ModMatrix.cpp \
src/sfizz/modulations/sources/ADSREnvelope.cpp \ src/sfizz/modulations/sources/ADSREnvelope.cpp \
src/sfizz/modulations/sources/ChannelAftertouch.cpp \
src/sfizz/modulations/sources/Controller.cpp \ src/sfizz/modulations/sources/Controller.cpp \
src/sfizz/modulations/sources/FlexEnvelope.cpp \ src/sfizz/modulations/sources/FlexEnvelope.cpp \
src/sfizz/modulations/sources/LFO.cpp \ src/sfizz/modulations/sources/LFO.cpp \

View file

@ -131,7 +131,7 @@ protected:
// The special member functions are not thread-safe. // The special member functions are not thread-safe.
AtomicQueueCommon() noexcept = default; AtomicQueueCommon() = default;
AtomicQueueCommon(AtomicQueueCommon const& b) noexcept AtomicQueueCommon(AtomicQueueCommon const& b) noexcept
: head_(b.head_.load(X)) : head_(b.head_.load(X))
@ -403,7 +403,7 @@ class AtomicQueue2 : public AtomicQueueCommon<AtomicQueue2<T, SIZE, MINIMIZE_CON
public: public:
using value_type = T; using value_type = T;
AtomicQueue2() noexcept = default; AtomicQueue2() = default;
AtomicQueue2(AtomicQueue2 const&) = delete; AtomicQueue2(AtomicQueue2 const&) = delete;
AtomicQueue2& operator=(AtomicQueue2 const&) = delete; AtomicQueue2& operator=(AtomicQueue2 const&) = delete;
}; };

View file

@ -761,6 +761,11 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
(int)msg[1], (int)msg[1],
msg[2]); msg[2]);
break; break;
case LV2_MIDI_MSG_CHANNEL_PRESSURE:
sfizz_send_aftertouch(self->synth,
(int)ev->time.frames,
msg[1]);
break;
case LV2_MIDI_MSG_BENDER: case LV2_MIDI_MSG_BENDER:
sfizz_send_pitch_wheel(self->synth, sfizz_send_pitch_wheel(self->synth,
(int)ev->time.frames, (int)ev->time.frames,

View file

@ -33,6 +33,7 @@ set(SFIZZ_HEADERS
sfizz/modulations/ModMatrix.h sfizz/modulations/ModMatrix.h
sfizz/modulations/ModGenerator.h sfizz/modulations/ModGenerator.h
sfizz/modulations/sources/ADSREnvelope.h sfizz/modulations/sources/ADSREnvelope.h
sfizz/modulations/sources/ChannelAftertouch.h
sfizz/modulations/sources/Controller.h sfizz/modulations/sources/Controller.h
sfizz/modulations/sources/FlexEnvelope.h sfizz/modulations/sources/FlexEnvelope.h
sfizz/modulations/sources/LFO.h sfizz/modulations/sources/LFO.h
@ -157,9 +158,10 @@ set(SFIZZ_SOURCES
sfizz/modulations/ModKey.cpp sfizz/modulations/ModKey.cpp
sfizz/modulations/ModKeyHash.cpp sfizz/modulations/ModKeyHash.cpp
sfizz/modulations/ModMatrix.cpp sfizz/modulations/ModMatrix.cpp
sfizz/modulations/sources/ADSREnvelope.cpp
sfizz/modulations/sources/ChannelAftertouch.cpp
sfizz/modulations/sources/Controller.cpp sfizz/modulations/sources/Controller.cpp
sfizz/modulations/sources/FlexEnvelope.cpp sfizz/modulations/sources/FlexEnvelope.cpp
sfizz/modulations/sources/ADSREnvelope.cpp
sfizz/modulations/sources/LFO.cpp sfizz/modulations/sources/LFO.cpp
sfizz/effects/Nothing.cpp sfizz/effects/Nothing.cpp
sfizz/effects/Filter.cpp sfizz/effects/Filter.cpp

View file

@ -24,6 +24,19 @@ enum class Oversampling: int {
x8 = 8 x8 = 8
}; };
enum ExtendedCCs {
pitchBend = 128,
channelAftertouch,
polyphonicAftertouch,
noteOnVelocity,
noteOffVelocity,
keyboardNoteNumber,
keyboardNoteGate,
unipolarRandom,
bipolarRandom,
alternate
};
namespace config { namespace config {
constexpr float defaultSampleRate { 48000 }; constexpr float defaultSampleRate { 48000 };
constexpr float maxSampleRate { 192000 }; constexpr float maxSampleRate { 192000 };

View file

@ -56,28 +56,33 @@ void sfz::MidiState::setSampleRate(float sampleRate) noexcept
void sfz::MidiState::advanceTime(int numSamples) noexcept void sfz::MidiState::advanceTime(int numSamples) noexcept
{ {
auto clearEvents = [] (EventVector& events) {
ASSERT(!events.empty()); // CC event vectors should never be empty
events.front().value = events.back().value;
events.front().delay = 0;
events.resize(1);
};
internalClock += numSamples; internalClock += numSamples;
for (auto& ccEvents : cc) { for (auto& ccEvents : cc)
ASSERT(!ccEvents.empty()); // CC event vectors should never be empty clearEvents(ccEvents);
ccEvents.front().value = ccEvents.back().value;
ccEvents.front().delay = 0; clearEvents(pitchEvents);
ccEvents.resize(1); clearEvents(channelAftertouchEvents);
}
ASSERT(!pitchEvents.empty());
pitchEvents.front().value = pitchEvents.back().value;
pitchEvents.front().delay = 0;
pitchEvents.resize(1);
} }
void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept
{ {
auto updateEventBufferSize = [=] (EventVector& events) {
events.shrink_to_fit();
events.reserve(samplesPerBlock);
};
this->samplesPerBlock = samplesPerBlock; this->samplesPerBlock = samplesPerBlock;
for (auto& ccEvents : cc) { for (auto& ccEvents : cc)
ccEvents.shrink_to_fit(); updateEventBufferSize(ccEvents);
ccEvents.reserve(samplesPerBlock);
} updateEventBufferSize(pitchEvents);
pitchEvents.shrink_to_fit(); updateEventBufferSize(channelAftertouchEvents);
pitchEvents.reserve(samplesPerBlock);
} }
float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const
@ -100,15 +105,19 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
return lastNoteVelocities[noteNumber]; return lastNoteVelocities[noteNumber];
} }
void sfz::MidiState::insertEventInVector(EventVector& events, int delay, float value)
{
const auto insertionPoint = absl::c_upper_bound(events, delay, MidiEventDelayComparator {});
if (insertionPoint == events.end() || insertionPoint->delay != delay)
events.insert(insertionPoint, { delay, value });
else
insertionPoint->value = value;
}
void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept void sfz::MidiState::pitchBendEvent(int delay, float pitchBendValue) noexcept
{ {
ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f); ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f);
insertEventInVector(pitchEvents, delay, pitchBendValue);
const auto insertionPoint = absl::c_upper_bound(pitchEvents, delay, MidiEventDelayComparator {});
if (insertionPoint == pitchEvents.end() || insertionPoint->delay != delay)
pitchEvents.insert(insertionPoint, { delay, pitchBendValue });
else
insertionPoint->value = pitchBendValue;
} }
float sfz::MidiState::getPitchBend() const noexcept float sfz::MidiState::getPitchBend() const noexcept
@ -117,20 +126,27 @@ float sfz::MidiState::getPitchBend() const noexcept
return pitchEvents.back().value; return pitchEvents.back().value;
} }
void sfz::MidiState::channelAftertouchEvent(int delay, float aftertouch) noexcept
{
ASSERT(aftertouch >= -1.0f && aftertouch <= 1.0f);
insertEventInVector(channelAftertouchEvents, delay, aftertouch);
}
float sfz::MidiState::getChannelAftertouch() const noexcept
{
ASSERT(channelAftertouchEvents.size() > 0);
return channelAftertouchEvents.back().value;
}
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); ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventDelayComparator {}); insertEventInVector(cc[ccNumber], delay, ccValue);
if (insertionPoint == cc[ccNumber].end() || insertionPoint->delay != delay)
cc[ccNumber].insert(insertionPoint, { delay, ccValue });
else
insertionPoint->value = ccValue;
} }
float sfz::MidiState::getCCValue(int ccNumber) const noexcept float sfz::MidiState::getCCValue(int ccNumber) const noexcept
{ {
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs); ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
return cc[ccNumber].back().value; return cc[ccNumber].back().value;
} }
@ -139,13 +155,16 @@ void sfz::MidiState::reset() noexcept
for (auto& velocity: lastNoteVelocities) for (auto& velocity: lastNoteVelocities)
velocity = 0; velocity = 0;
for (auto& ccEvents : cc) { auto clearEvents = [] (EventVector& events) {
ccEvents.clear(); events.clear();
ccEvents.push_back({ 0, 0.0f }); events.push_back({ 0, 0.0f });
} };
pitchEvents.clear(); for (auto& ccEvents : cc)
pitchEvents.push_back({ 0, 0.0f }); clearEvents(ccEvents);
clearEvents(pitchEvents);
clearEvents(channelAftertouchEvents);
activeNotes = 0; activeNotes = 0;
internalClock = 0; internalClock = 0;
@ -173,3 +192,8 @@ const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
{ {
return pitchEvents; return pitchEvents;
} }
const sfz::EventVector& sfz::MidiState::getChannelAftertouchEvents() const noexcept
{
return channelAftertouchEvents;
}

View file

@ -98,6 +98,20 @@ public:
*/ */
float getPitchBend() const noexcept; float getPitchBend() const noexcept;
/**
* @brief Register a channel aftertouch event
*
* @param aftertouch
*/
void channelAftertouchEvent(int delay, float aftertouch) noexcept;
/**
* @brief Get the channel aftertouch status
* @return int
*/
float getChannelAftertouch() const noexcept;
/** /**
* @brief Register a CC event * @brief Register a CC event
* *
@ -135,8 +149,19 @@ public:
const EventVector& getCCEvents(int ccIdx) const noexcept; const EventVector& getCCEvents(int ccIdx) const noexcept;
const EventVector& getPitchEvents() const noexcept; const EventVector& getPitchEvents() const noexcept;
const EventVector& getChannelAftertouchEvents() const noexcept;
private: private:
/**
* @brief Insert events in a sorted event vector.
*
* @param events
* @param delay
* @param value
*/
void insertEventInVector(EventVector& events, int delay, float value);
int activeNotes { 0 }; int activeNotes { 0 };
/** /**
@ -175,6 +200,12 @@ private:
* @brief Pitch bend status * @brief Pitch bend status
*/ */
EventVector pitchEvents; EventVector pitchEvents;
/**
* @brief Aftertouch status
*/
EventVector channelAftertouchEvents;
float sampleRate { config::defaultSampleRate }; float sampleRate { config::defaultSampleRate };
int samplesPerBlock { config::defaultSamplesPerBlock }; int samplesPerBlock { config::defaultSamplesPerBlock };
unsigned internalClock { 0 }; unsigned internalClock { 0 };

View file

@ -652,6 +652,19 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
processGenericCc(opcode, Default::filterResonanceModRange, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex)); processGenericCc(opcode, Default::filterResonanceModRange, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex));
} }
break; break;
case hash("cutoff&_chanaft"):
{
const auto filterIndex = opcode.parameters.front() - 1;
if (!extendIfNecessary(filters, filterIndex + 1, Default::numFilters))
return false;
if (auto value = readOpcode(opcode.value, Default::filterCutoffModRange)) {
const ModKey source = ModKey::createNXYZ(ModId::ChannelAftertouch);
const ModKey target = ModKey::createNXYZ(ModId::FilCutoff, id, filterIndex);
getOrCreateConnection(source, target).sourceDepth = *value;
}
}
break;
case hash("fil&_keytrack"): // also fil_keytrack case hash("fil&_keytrack"): // also fil_keytrack
{ {
const auto filterIndex = opcode.parameters.front() - 1; const auto filterIndex = opcode.parameters.front() - 1;

View file

@ -54,6 +54,7 @@ struct MidiEvent {
int delay; int delay;
float value; float value;
}; };
using EventVector = std::vector<MidiEvent>; using EventVector = std::vector<MidiEvent>;
struct MidiEventDelayComparator { struct MidiEventDelayComparator {

View file

@ -63,6 +63,7 @@ Synth::Impl::Impl()
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));
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, resources_.midiState));
} }
Synth::Impl::~Impl() Synth::Impl::~Impl()
@ -1214,11 +1215,26 @@ void Synth::pitchWheel(int delay, int pitch) noexcept
voice.registerPitchWheel(delay, normalizedPitch); voice.registerPitchWheel(delay, normalizedPitch);
} }
} }
void Synth::aftertouch(int /* delay */, uint8_t /* aftertouch */) noexcept
void Synth::aftertouch(int delay, uint8_t aftertouch) noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration }; ScopedTiming logger { impl.dispatchDuration_, ScopedTiming::Operation::addToDuration };
const auto normalizedAftertouch = normalize7Bits(aftertouch);
impl.resources_.midiState.channelAftertouchEvent(delay, normalizedAftertouch);
impl.resources_.midiState.ccEvent(delay, ExtendedCCs::channelAftertouch, normalizedAftertouch);
for (auto& region : impl.regions_) {
region->registerAftertouch(aftertouch);
}
for (auto& voice : impl.voiceManager_) {
voice.registerAftertouch(delay, aftertouch);
}
} }
void Synth::tempo(int delay, float secondsPerBeat) noexcept void Synth::tempo(int delay, float secondsPerBeat) noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
@ -1226,6 +1242,7 @@ void Synth::tempo(int delay, float secondsPerBeat) noexcept
impl.resources_.beatClock.setTempo(delay, secondsPerBeat); impl.resources_.beatClock.setTempo(delay, secondsPerBeat);
} }
void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit) void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit)
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
@ -1233,6 +1250,7 @@ void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit)
impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit)); impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit));
} }
void Synth::timePosition(int delay, int bar, double barBeat) void Synth::timePosition(int delay, int bar, double barBeat)
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
@ -1240,6 +1258,7 @@ void Synth::timePosition(int delay, int bar, double barBeat)
impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat)); impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat));
} }
void Synth::playbackState(int delay, int playbackState) void Synth::playbackState(int delay, int playbackState)
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
@ -1253,16 +1272,19 @@ int Synth::getNumRegions() const noexcept
Impl& impl = *impl_; Impl& impl = *impl_;
return static_cast<int>(impl.regions_.size()); return static_cast<int>(impl.regions_.size());
} }
int Synth::getNumGroups() const noexcept int Synth::getNumGroups() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
return impl.numGroups_; return impl.numGroups_;
} }
int Synth::getNumMasters() const noexcept int Synth::getNumMasters() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
return impl.numMasters_; return impl.numMasters_;
} }
int Synth::getNumCurves() const noexcept int Synth::getNumCurves() const noexcept
{ {
Impl& impl = *impl_; Impl& impl = *impl_;
@ -1563,6 +1585,9 @@ void Synth::Impl::setupModMatrix()
case ModId::FilEG: case ModId::FilEG:
gen = genADSREnvelope_.get(); gen = genADSREnvelope_.get();
break; break;
case ModId::ChannelAftertouch:
gen = genChannelAftertouch_.get();
break;
default: default:
DBG("[sfizz] Have unknown type of source generator"); DBG("[sfizz] Have unknown type of source generator");
break; break;

View file

@ -8,6 +8,7 @@
#include "modulations/sources/ADSREnvelope.h" #include "modulations/sources/ADSREnvelope.h"
#include "modulations/sources/Controller.h" #include "modulations/sources/Controller.h"
#include "modulations/sources/FlexEnvelope.h" #include "modulations/sources/FlexEnvelope.h"
#include "modulations/sources/ChannelAftertouch.h"
#include "modulations/sources/LFO.h" #include "modulations/sources/LFO.h"
#include "utility/BitArray.h" #include "utility/BitArray.h"
@ -265,6 +266,7 @@ struct Synth::Impl final: public Parser::Listener {
std::unique_ptr<LFOSource> genLFO_; std::unique_ptr<LFOSource> genLFO_;
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope_; std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope_;
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope_; std::unique_ptr<ADSREnvelopeSource> genADSREnvelope_;
std::unique_ptr<ChannelAftertouchSource> genChannelAftertouch_;
// Settings per voice // Settings per voice
struct { struct {

View file

@ -36,6 +36,8 @@ int ModIds::flags(ModId id) noexcept
return kModIsPerVoice; return kModIsPerVoice;
case ModId::FilEG: case ModId::FilEG:
return kModIsPerVoice; return kModIsPerVoice;
case ModId::ChannelAftertouch:
return kModIsPerCycle;
// targets // targets
case ModId::MasterAmplitude: case ModId::MasterAmplitude:

View file

@ -26,6 +26,7 @@ enum class ModId : int {
AmpEG, AmpEG,
PitchEG, PitchEG,
FilEG, FilEG,
ChannelAftertouch,
_SourcesEnd, _SourcesEnd,

View file

@ -105,6 +105,8 @@ std::string ModKey::toString() const
return absl::StrCat("PitchEG {", region_.number(), "}"); return absl::StrCat("PitchEG {", region_.number(), "}");
case ModId::FilEG: case ModId::FilEG:
return absl::StrCat("FilterEG {", region_.number(), "}"); return absl::StrCat("FilterEG {", region_.number(), "}");
case ModId::ChannelAftertouch:
return absl::StrCat("ChannelAftertouch");
case ModId::MasterAmplitude: case ModId::MasterAmplitude:
return absl::StrCat("MasterAmplitude {", region_.number(), "}"); return absl::StrCat("MasterAmplitude {", region_.number(), "}");

View file

@ -29,7 +29,7 @@ public:
: id_(id), region_(region), params_(params), flags_(ModIds::flags(id_)) {} : id_(id), region_(region), params_(params), flags_(ModIds::flags(id_)) {}
static ModKey createCC(uint16_t cc, uint8_t curve, uint8_t smooth, float step); static ModKey createCC(uint16_t cc, uint8_t curve, uint8_t smooth, float step);
static ModKey createNXYZ(ModId id, NumericId<Region> region, uint8_t N = 0, uint8_t X = 0, uint8_t Y = 0, uint8_t Z = 0); static ModKey createNXYZ(ModId id, NumericId<Region> region = {}, uint8_t N = 0, uint8_t X = 0, uint8_t Y = 0, uint8_t Z = 0);
explicit operator bool() const noexcept { return id_ != ModId(); } explicit operator bool() const noexcept { return id_ != ModId(); }

View file

@ -0,0 +1,42 @@
// 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
#include "ChannelAftertouch.h"
#include "../../ModifierHelpers.h"
#include "../../ADSREnvelope.h"
// TODO(jpc): also matrix the ampeg
namespace sfz {
ChannelAftertouchSource::ChannelAftertouchSource(VoiceManager& manager, MidiState& state)
: voiceManager_(manager), midiState_(state)
{
}
void ChannelAftertouchSource::init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
{
UNUSED(sourceKey);
UNUSED(voiceId);
UNUSED(delay);
}
void ChannelAftertouchSource::release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay)
{
UNUSED(sourceKey);
UNUSED(voiceId);
UNUSED(delay);
}
void ChannelAftertouchSource::generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer)
{
UNUSED(sourceKey);
UNUSED(voiceId);
const EventVector& events = midiState_.getChannelAftertouchEvents();
linearEnvelope(events, buffer, [](float x) { return x; });
}
} // namespace sfz

View file

@ -0,0 +1,27 @@
// 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
#include "../ModGenerator.h"
#include "../../VoiceManager.h"
#include "../../MidiState.h"
namespace sfz {
class Synth;
class ChannelAftertouchSource : public ModGenerator {
public:
explicit ChannelAftertouchSource(VoiceManager &manager, MidiState& state);
void init(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void release(const ModKey& sourceKey, NumericId<Voice> voiceId, unsigned delay) override;
void generate(const ModKey& sourceKey, NumericId<Voice> voiceId, absl::Span<float> buffer) override;
private:
VoiceManager& voiceManager_;
MidiState& midiState_;
};
} // namespace sfz

View file

@ -339,3 +339,18 @@ TEST_CASE("[Modulations] Override the default pan controller")
R"("Controller 7 {curve=4, smooth=10, step=0}" -> "Amplitude {0}")", R"("Controller 7 {curve=4, smooth=10, step=0}" -> "Amplitude {0}")",
})); }));
} }
TEST_CASE("[Modulations] Aftertouch connections")
{
sfz::Synth synth;
synth.loadSfzString("/modulation.sfz", R"(
<region> sample=*sine cutoff_chanaft=1000
<region> sample=*sine cutoff2_chanaft=1000
)");
const std::string graph = synth.getResources().modMatrix.toDotGraph();
REQUIRE(graph == createDefaultGraph({
R"("ChannelAftertouch" -> "FilterCutoff {0, N=1}")",
R"("ChannelAftertouch" -> "FilterCutoff {1, N=2}")",
}, 2));
}