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:
parent
02c5de1237
commit
6deb3d37d7
19 changed files with 249 additions and 47 deletions
|
|
@ -78,34 +78,30 @@ int process(jack_nframes_t numFrames, void* arg)
|
|||
|
||||
switch (midi::status(event.buffer[0])) {
|
||||
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]);
|
||||
break;
|
||||
case midi::noteOn:
|
||||
if (event.buffer[2] == 0)
|
||||
goto noteoff;
|
||||
// DBG("[MIDI] Note " << +event.buffer[1] << " ON at time " << event.time);
|
||||
synth->noteOn(event.time, event.buffer[1], event.buffer[2]);
|
||||
break;
|
||||
case midi::polyphonicPressure:
|
||||
// DBG("[MIDI] Polyphonic pressure on at time " << event.time);
|
||||
// Not implemented
|
||||
break;
|
||||
case midi::controlChange:
|
||||
// DBG("[MIDI] CC " << +event.buffer[1] << " at time " << event.time);
|
||||
synth->cc(event.time, event.buffer[1], event.buffer[2]);
|
||||
break;
|
||||
case midi::programChange:
|
||||
// DBG("[MIDI] Program change at time " << event.time);
|
||||
// Not implemented
|
||||
break;
|
||||
case midi::channelPressure:
|
||||
// DBG("[MIDI] Channel pressure at time " << event.time);
|
||||
synth->aftertouch(event.time, event.buffer[1]);
|
||||
break;
|
||||
case midi::pitchBend:
|
||||
synth->pitchWheel(event.time, midi::buildAndCenterPitch(event.buffer[1], event.buffer[2]));
|
||||
// DBG("[MIDI] Pitch bend at time " << event.time);
|
||||
break;
|
||||
case midi::systemMessage:
|
||||
// DBG("[MIDI] System message at time " << event.time);
|
||||
// Not implemented
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ SFIZZ_SOURCES = \
|
|||
src/sfizz/modulations/ModKeyHash.cpp \
|
||||
src/sfizz/modulations/ModMatrix.cpp \
|
||||
src/sfizz/modulations/sources/ADSREnvelope.cpp \
|
||||
src/sfizz/modulations/sources/ChannelAftertouch.cpp \
|
||||
src/sfizz/modulations/sources/Controller.cpp \
|
||||
src/sfizz/modulations/sources/FlexEnvelope.cpp \
|
||||
src/sfizz/modulations/sources/LFO.cpp \
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ protected:
|
|||
|
||||
// The special member functions are not thread-safe.
|
||||
|
||||
AtomicQueueCommon() noexcept = default;
|
||||
AtomicQueueCommon() = default;
|
||||
|
||||
AtomicQueueCommon(AtomicQueueCommon const& b) noexcept
|
||||
: head_(b.head_.load(X))
|
||||
|
|
@ -403,7 +403,7 @@ class AtomicQueue2 : public AtomicQueueCommon<AtomicQueue2<T, SIZE, MINIMIZE_CON
|
|||
public:
|
||||
using value_type = T;
|
||||
|
||||
AtomicQueue2() noexcept = default;
|
||||
AtomicQueue2() = default;
|
||||
AtomicQueue2(AtomicQueue2 const&) = delete;
|
||||
AtomicQueue2& operator=(AtomicQueue2 const&) = delete;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -761,6 +761,11 @@ sfizz_lv2_process_midi_event(sfizz_plugin_t *self, const LV2_Atom_Event *ev)
|
|||
(int)msg[1],
|
||||
msg[2]);
|
||||
break;
|
||||
case LV2_MIDI_MSG_CHANNEL_PRESSURE:
|
||||
sfizz_send_aftertouch(self->synth,
|
||||
(int)ev->time.frames,
|
||||
msg[1]);
|
||||
break;
|
||||
case LV2_MIDI_MSG_BENDER:
|
||||
sfizz_send_pitch_wheel(self->synth,
|
||||
(int)ev->time.frames,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ set(SFIZZ_HEADERS
|
|||
sfizz/modulations/ModMatrix.h
|
||||
sfizz/modulations/ModGenerator.h
|
||||
sfizz/modulations/sources/ADSREnvelope.h
|
||||
sfizz/modulations/sources/ChannelAftertouch.h
|
||||
sfizz/modulations/sources/Controller.h
|
||||
sfizz/modulations/sources/FlexEnvelope.h
|
||||
sfizz/modulations/sources/LFO.h
|
||||
|
|
@ -157,9 +158,10 @@ set(SFIZZ_SOURCES
|
|||
sfizz/modulations/ModKey.cpp
|
||||
sfizz/modulations/ModKeyHash.cpp
|
||||
sfizz/modulations/ModMatrix.cpp
|
||||
sfizz/modulations/sources/ADSREnvelope.cpp
|
||||
sfizz/modulations/sources/ChannelAftertouch.cpp
|
||||
sfizz/modulations/sources/Controller.cpp
|
||||
sfizz/modulations/sources/FlexEnvelope.cpp
|
||||
sfizz/modulations/sources/ADSREnvelope.cpp
|
||||
sfizz/modulations/sources/LFO.cpp
|
||||
sfizz/effects/Nothing.cpp
|
||||
sfizz/effects/Filter.cpp
|
||||
|
|
|
|||
|
|
@ -24,6 +24,19 @@ enum class Oversampling: int {
|
|||
x8 = 8
|
||||
};
|
||||
|
||||
enum ExtendedCCs {
|
||||
pitchBend = 128,
|
||||
channelAftertouch,
|
||||
polyphonicAftertouch,
|
||||
noteOnVelocity,
|
||||
noteOffVelocity,
|
||||
keyboardNoteNumber,
|
||||
keyboardNoteGate,
|
||||
unipolarRandom,
|
||||
bipolarRandom,
|
||||
alternate
|
||||
};
|
||||
|
||||
namespace config {
|
||||
constexpr float defaultSampleRate { 48000 };
|
||||
constexpr float maxSampleRate { 192000 };
|
||||
|
|
|
|||
|
|
@ -56,28 +56,33 @@ void sfz::MidiState::setSampleRate(float sampleRate) 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;
|
||||
for (auto& ccEvents : cc) {
|
||||
ASSERT(!ccEvents.empty()); // CC event vectors should never be empty
|
||||
ccEvents.front().value = ccEvents.back().value;
|
||||
ccEvents.front().delay = 0;
|
||||
ccEvents.resize(1);
|
||||
}
|
||||
ASSERT(!pitchEvents.empty());
|
||||
pitchEvents.front().value = pitchEvents.back().value;
|
||||
pitchEvents.front().delay = 0;
|
||||
pitchEvents.resize(1);
|
||||
for (auto& ccEvents : cc)
|
||||
clearEvents(ccEvents);
|
||||
|
||||
clearEvents(pitchEvents);
|
||||
clearEvents(channelAftertouchEvents);
|
||||
}
|
||||
|
||||
void sfz::MidiState::setSamplesPerBlock(int samplesPerBlock) noexcept
|
||||
{
|
||||
auto updateEventBufferSize = [=] (EventVector& events) {
|
||||
events.shrink_to_fit();
|
||||
events.reserve(samplesPerBlock);
|
||||
};
|
||||
this->samplesPerBlock = samplesPerBlock;
|
||||
for (auto& ccEvents : cc) {
|
||||
ccEvents.shrink_to_fit();
|
||||
ccEvents.reserve(samplesPerBlock);
|
||||
}
|
||||
pitchEvents.shrink_to_fit();
|
||||
pitchEvents.reserve(samplesPerBlock);
|
||||
for (auto& ccEvents : cc)
|
||||
updateEventBufferSize(ccEvents);
|
||||
|
||||
updateEventBufferSize(pitchEvents);
|
||||
updateEventBufferSize(channelAftertouchEvents);
|
||||
}
|
||||
|
||||
float sfz::MidiState::getNoteDuration(int noteNumber, int delay) const
|
||||
|
|
@ -100,15 +105,19 @@ float sfz::MidiState::getNoteVelocity(int noteNumber) const noexcept
|
|||
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
|
||||
{
|
||||
ASSERT(pitchBendValue >= -1.0f && pitchBendValue <= 1.0f);
|
||||
|
||||
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;
|
||||
insertEventInVector(pitchEvents, delay, pitchBendValue);
|
||||
}
|
||||
|
||||
float sfz::MidiState::getPitchBend() const noexcept
|
||||
|
|
@ -117,20 +126,27 @@ float sfz::MidiState::getPitchBend() const noexcept
|
|||
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
|
||||
{
|
||||
ASSERT(ccValue >= 0.0 && ccValue <= 1.0);
|
||||
const auto insertionPoint = absl::c_upper_bound(cc[ccNumber], delay, MidiEventDelayComparator {});
|
||||
if (insertionPoint == cc[ccNumber].end() || insertionPoint->delay != delay)
|
||||
cc[ccNumber].insert(insertionPoint, { delay, ccValue });
|
||||
else
|
||||
insertionPoint->value = ccValue;
|
||||
insertEventInVector(cc[ccNumber], delay, ccValue);
|
||||
}
|
||||
|
||||
float sfz::MidiState::getCCValue(int ccNumber) const noexcept
|
||||
{
|
||||
ASSERT(ccNumber >= 0 && ccNumber < config::numCCs);
|
||||
|
||||
return cc[ccNumber].back().value;
|
||||
}
|
||||
|
||||
|
|
@ -139,13 +155,16 @@ void sfz::MidiState::reset() noexcept
|
|||
for (auto& velocity: lastNoteVelocities)
|
||||
velocity = 0;
|
||||
|
||||
for (auto& ccEvents : cc) {
|
||||
ccEvents.clear();
|
||||
ccEvents.push_back({ 0, 0.0f });
|
||||
}
|
||||
auto clearEvents = [] (EventVector& events) {
|
||||
events.clear();
|
||||
events.push_back({ 0, 0.0f });
|
||||
};
|
||||
|
||||
pitchEvents.clear();
|
||||
pitchEvents.push_back({ 0, 0.0f });
|
||||
for (auto& ccEvents : cc)
|
||||
clearEvents(ccEvents);
|
||||
|
||||
clearEvents(pitchEvents);
|
||||
clearEvents(channelAftertouchEvents);
|
||||
|
||||
activeNotes = 0;
|
||||
internalClock = 0;
|
||||
|
|
@ -173,3 +192,8 @@ const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
|
|||
{
|
||||
return pitchEvents;
|
||||
}
|
||||
|
||||
const sfz::EventVector& sfz::MidiState::getChannelAftertouchEvents() const noexcept
|
||||
{
|
||||
return channelAftertouchEvents;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,20 @@ public:
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
|
@ -135,8 +149,19 @@ public:
|
|||
|
||||
const EventVector& getCCEvents(int ccIdx) const noexcept;
|
||||
const EventVector& getPitchEvents() const noexcept;
|
||||
const EventVector& getChannelAftertouchEvents() const noexcept;
|
||||
|
||||
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 };
|
||||
|
||||
/**
|
||||
|
|
@ -175,6 +200,12 @@ private:
|
|||
* @brief Pitch bend status
|
||||
*/
|
||||
EventVector pitchEvents;
|
||||
|
||||
/**
|
||||
* @brief Aftertouch status
|
||||
*/
|
||||
EventVector channelAftertouchEvents;
|
||||
|
||||
float sampleRate { config::defaultSampleRate };
|
||||
int samplesPerBlock { config::defaultSamplesPerBlock };
|
||||
unsigned internalClock { 0 };
|
||||
|
|
|
|||
|
|
@ -652,6 +652,19 @@ bool sfz::Region::parseOpcode(const Opcode& rawOpcode)
|
|||
processGenericCc(opcode, Default::filterResonanceModRange, ModKey::createNXYZ(ModId::FilResonance, id, filterIndex));
|
||||
}
|
||||
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
|
||||
{
|
||||
const auto filterIndex = opcode.parameters.front() - 1;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ struct MidiEvent {
|
|||
int delay;
|
||||
float value;
|
||||
};
|
||||
|
||||
using EventVector = std::vector<MidiEvent>;
|
||||
|
||||
struct MidiEventDelayComparator {
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ Synth::Impl::Impl()
|
|||
genLFO_.reset(new LFOSource(voiceManager_));
|
||||
genFlexEnvelope_.reset(new FlexEnvelopeSource(voiceManager_));
|
||||
genADSREnvelope_.reset(new ADSREnvelopeSource(voiceManager_, resources_.midiState));
|
||||
genChannelAftertouch_.reset(new ChannelAftertouchSource(voiceManager_, resources_.midiState));
|
||||
}
|
||||
|
||||
Synth::Impl::~Impl()
|
||||
|
|
@ -1214,11 +1215,26 @@ void Synth::pitchWheel(int delay, int pitch) noexcept
|
|||
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_;
|
||||
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
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -1226,6 +1242,7 @@ void Synth::tempo(int delay, float secondsPerBeat) noexcept
|
|||
|
||||
impl.resources_.beatClock.setTempo(delay, secondsPerBeat);
|
||||
}
|
||||
|
||||
void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -1233,6 +1250,7 @@ void Synth::timeSignature(int delay, int beatsPerBar, int beatUnit)
|
|||
|
||||
impl.resources_.beatClock.setTimeSignature(delay, TimeSignature(beatsPerBar, beatUnit));
|
||||
}
|
||||
|
||||
void Synth::timePosition(int delay, int bar, double barBeat)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -1240,6 +1258,7 @@ void Synth::timePosition(int delay, int bar, double barBeat)
|
|||
|
||||
impl.resources_.beatClock.setTimePosition(delay, BBT(bar, barBeat));
|
||||
}
|
||||
|
||||
void Synth::playbackState(int delay, int playbackState)
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -1253,16 +1272,19 @@ int Synth::getNumRegions() const noexcept
|
|||
Impl& impl = *impl_;
|
||||
return static_cast<int>(impl.regions_.size());
|
||||
}
|
||||
|
||||
int Synth::getNumGroups() const noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
return impl.numGroups_;
|
||||
}
|
||||
|
||||
int Synth::getNumMasters() const noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
return impl.numMasters_;
|
||||
}
|
||||
|
||||
int Synth::getNumCurves() const noexcept
|
||||
{
|
||||
Impl& impl = *impl_;
|
||||
|
|
@ -1563,6 +1585,9 @@ void Synth::Impl::setupModMatrix()
|
|||
case ModId::FilEG:
|
||||
gen = genADSREnvelope_.get();
|
||||
break;
|
||||
case ModId::ChannelAftertouch:
|
||||
gen = genChannelAftertouch_.get();
|
||||
break;
|
||||
default:
|
||||
DBG("[sfizz] Have unknown type of source generator");
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "modulations/sources/ADSREnvelope.h"
|
||||
#include "modulations/sources/Controller.h"
|
||||
#include "modulations/sources/FlexEnvelope.h"
|
||||
#include "modulations/sources/ChannelAftertouch.h"
|
||||
#include "modulations/sources/LFO.h"
|
||||
#include "utility/BitArray.h"
|
||||
|
||||
|
|
@ -265,6 +266,7 @@ struct Synth::Impl final: public Parser::Listener {
|
|||
std::unique_ptr<LFOSource> genLFO_;
|
||||
std::unique_ptr<FlexEnvelopeSource> genFlexEnvelope_;
|
||||
std::unique_ptr<ADSREnvelopeSource> genADSREnvelope_;
|
||||
std::unique_ptr<ChannelAftertouchSource> genChannelAftertouch_;
|
||||
|
||||
// Settings per voice
|
||||
struct {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ int ModIds::flags(ModId id) noexcept
|
|||
return kModIsPerVoice;
|
||||
case ModId::FilEG:
|
||||
return kModIsPerVoice;
|
||||
case ModId::ChannelAftertouch:
|
||||
return kModIsPerCycle;
|
||||
|
||||
// targets
|
||||
case ModId::MasterAmplitude:
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ enum class ModId : int {
|
|||
AmpEG,
|
||||
PitchEG,
|
||||
FilEG,
|
||||
ChannelAftertouch,
|
||||
|
||||
_SourcesEnd,
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ std::string ModKey::toString() const
|
|||
return absl::StrCat("PitchEG {", region_.number(), "}");
|
||||
case ModId::FilEG:
|
||||
return absl::StrCat("FilterEG {", region_.number(), "}");
|
||||
case ModId::ChannelAftertouch:
|
||||
return absl::StrCat("ChannelAftertouch");
|
||||
|
||||
case ModId::MasterAmplitude:
|
||||
return absl::StrCat("MasterAmplitude {", region_.number(), "}");
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public:
|
|||
: 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 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(); }
|
||||
|
||||
|
|
|
|||
42
src/sfizz/modulations/sources/ChannelAftertouch.cpp
Normal file
42
src/sfizz/modulations/sources/ChannelAftertouch.cpp
Normal 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
|
||||
27
src/sfizz/modulations/sources/ChannelAftertouch.h
Normal file
27
src/sfizz/modulations/sources/ChannelAftertouch.h
Normal 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
|
||||
|
|
@ -339,3 +339,18 @@ TEST_CASE("[Modulations] Override the default pan controller")
|
|||
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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue