Skip to content

Commit

Permalink
Preliminary work for aftertouch handling
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
paulfd committed Feb 6, 2021
1 parent 70d26b5 commit 7d740e7
Show file tree
Hide file tree
Showing 19 changed files with 249 additions and 47 deletions.
12 changes: 4 additions & 8 deletions clients/jack_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
1 change: 1 addition & 0 deletions common.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
4 changes: 2 additions & 2 deletions external/atomic_queue/include/atomic_queue/atomic_queue.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ class AtomicQueueCommon {

// The special member functions are not thread-safe.

AtomicQueueCommon() noexcept = default;
AtomicQueueCommon() = default;

AtomicQueueCommon(AtomicQueueCommon const& b) noexcept
: head_(b.head_.load(X))
Expand Down Expand Up @@ -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;
};
Expand Down
5 changes: 5 additions & 0 deletions lv2/sfizz.c
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,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,
Expand Down
4 changes: 3 additions & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions src/sfizz/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down
92 changes: 58 additions & 34 deletions src/sfizz/MidiState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
}

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

for (auto& ccEvents : cc)
clearEvents(ccEvents);

pitchEvents.clear();
pitchEvents.push_back({ 0, 0.0f });
clearEvents(pitchEvents);
clearEvents(channelAftertouchEvents);

activeNotes = 0;
internalClock = 0;
Expand Down Expand Up @@ -173,3 +192,8 @@ const sfz::EventVector& sfz::MidiState::getPitchEvents() const noexcept
{
return pitchEvents;
}

const sfz::EventVector& sfz::MidiState::getChannelAftertouchEvents() const noexcept
{
return channelAftertouchEvents;
}
31 changes: 31 additions & 0 deletions src/sfizz/MidiState.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ class MidiState
*/
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
*
Expand Down Expand Up @@ -135,8 +149,19 @@ class MidiState

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

/**
Expand Down Expand Up @@ -175,6 +200,12 @@ class MidiState
* @brief Pitch bend status
*/
EventVector pitchEvents;

/**
* @brief Aftertouch status
*/
EventVector channelAftertouchEvents;

float sampleRate { config::defaultSampleRate };
int samplesPerBlock { config::defaultSamplesPerBlock };
unsigned internalClock { 0 };
Expand Down
13 changes: 13 additions & 0 deletions src/sfizz/Region.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/sfizz/SfzHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ struct MidiEvent {
int delay;
float value;
};

using EventVector = std::vector<MidiEvent>;

struct MidiEventDelayComparator {
Expand Down
Loading

0 comments on commit 7d740e7

Please sign in to comment.