Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preliminary work for aftertouch handling #630

Merged
merged 3 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 plugins/lv2/sfizz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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