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])) {
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ enum class ModId : int {
|
||||||
AmpEG,
|
AmpEG,
|
||||||
PitchEG,
|
PitchEG,
|
||||||
FilEG,
|
FilEG,
|
||||||
|
ChannelAftertouch,
|
||||||
|
|
||||||
_SourcesEnd,
|
_SourcesEnd,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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(), "}");
|
||||||
|
|
|
||||||
|
|
@ -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(); }
|
||||||
|
|
||||||
|
|
|
||||||
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}")",
|
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