diff --git a/src/common/serialization/clap/events.cpp b/src/common/serialization/clap/events.cpp
new file mode 100644
index 00000000..2dca513e
--- /dev/null
+++ b/src/common/serialization/clap/events.cpp
@@ -0,0 +1,197 @@
+// yabridge: a Wine plugin bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "events.h"
+
+namespace clap {
+namespace events {
+
+std::optional Event::parse(const clap_event_header_t& generic_event) {
+ std::optional payload;
+ if (generic_event.space_id == CLAP_CORE_EVENT_SPACE_ID) {
+ switch (generic_event.type) {
+ case CLAP_EVENT_NOTE_ON: {
+ const auto& event =
+ reinterpret_cast(generic_event);
+ payload = payload::Note{
+ .event_type = payload::NoteEventType::On,
+ .note_id = event.note_id,
+ .port_index = event.port_index,
+ .channel = event.channel,
+ .key = event.key,
+ .velocity = event.velocity,
+ };
+ } break;
+ case CLAP_EVENT_NOTE_OFF: {
+ const auto& event =
+ reinterpret_cast(generic_event);
+ payload = payload::Note{
+ .event_type = payload::NoteEventType::Off,
+ .note_id = event.note_id,
+ .port_index = event.port_index,
+ .channel = event.channel,
+ .key = event.key,
+ .velocity = event.velocity,
+ };
+ } break;
+ case CLAP_EVENT_NOTE_CHOKE: {
+ const auto& event =
+ reinterpret_cast(generic_event);
+ payload = payload::Note{
+ .event_type = payload::NoteEventType::Choke,
+ .note_id = event.note_id,
+ .port_index = event.port_index,
+ .channel = event.channel,
+ .key = event.key,
+ .velocity = event.velocity,
+ };
+ } break;
+ case CLAP_EVENT_NOTE_END: {
+ const auto& event =
+ reinterpret_cast(generic_event);
+ payload = payload::Note{
+ .event_type = payload::NoteEventType::End,
+ .note_id = event.note_id,
+ .port_index = event.port_index,
+ .channel = event.channel,
+ .key = event.key,
+ .velocity = event.velocity,
+ };
+ } break;
+ case CLAP_EVENT_NOTE_EXPRESSION: {
+ const auto& event =
+ reinterpret_cast(
+ generic_event);
+ payload = payload::NoteExpression{
+ .expression_id = event.expression_id,
+ .note_id = event.note_id,
+ .port_index = event.port_index,
+ .channel = event.channel,
+ .key = event.key,
+ .value = event.value,
+ };
+ } break;
+ case CLAP_EVENT_PARAM_VALUE: {
+ const auto& event =
+ reinterpret_cast(
+ generic_event);
+ payload = payload::ParamValue{
+ .param_id = event.param_id,
+ .cookie = static_cast(
+ reinterpret_cast(event.cookie)),
+ .note_id = event.note_id,
+ .port_index = event.port_index,
+ .channel = event.channel,
+ .key = event.key,
+ .value = event.value,
+ };
+ } break;
+ case CLAP_EVENT_PARAM_MOD: {
+ const auto& event =
+ reinterpret_cast(
+ generic_event);
+ payload = payload::ParamMod{
+ .param_id = event.param_id,
+ .cookie = static_cast(
+ reinterpret_cast(event.cookie)),
+ .note_id = event.note_id,
+ .port_index = event.port_index,
+ .channel = event.channel,
+ .key = event.key,
+ .amount = event.amount,
+ };
+ } break;
+ case CLAP_EVENT_PARAM_GESTURE_BEGIN: {
+ const auto& event =
+ reinterpret_cast(
+ generic_event);
+ payload = payload::ParamGesture{
+ .gesture_type = payload::ParamGestureType::Begin,
+ .param_id = event.param_id,
+ };
+ } break;
+ case CLAP_EVENT_PARAM_GESTURE_END: {
+ const auto& event =
+ reinterpret_cast(
+ generic_event);
+ payload = payload::ParamGesture{
+ .gesture_type = payload::ParamGestureType::End,
+ .param_id = event.param_id,
+ };
+ } break;
+ case CLAP_EVENT_TRANSPORT: {
+ const auto& event =
+ reinterpret_cast(
+ generic_event);
+ payload = payload::Transport{
+ .flags = event.flags,
+ .song_pos_beats = event.song_pos_beats,
+ .song_pos_seconds = event.song_pos_seconds,
+ .tempo = event.tempo,
+ .tempo_inc = event.tempo_inc,
+ .loop_start_beats = event.loop_start_beats,
+ .loop_end_beats = event.loop_end_beats,
+ .loop_start_seconds = event.loop_start_seconds,
+ .loop_end_seconds = event.loop_end_seconds,
+ .bar_start = event.bar_start,
+ .bar_number = event.bar_number,
+ .tsig_num = event.tsig_num,
+ .tsig_denom = event.tsig_denom,
+ };
+ } break;
+ case CLAP_EVENT_MIDI: {
+ const auto& event =
+ reinterpret_cast(generic_event);
+ payload = payload::Midi{
+ .port_index = event.port_index,
+ .data{event.data[0], event.data[1], event.data[2]},
+ };
+ } break;
+ case CLAP_EVENT_MIDI_SYSEX: {
+ const auto& event =
+ reinterpret_cast(
+ generic_event);
+ assert(event.buffer);
+ payload = payload::MidiSysex{
+ .port_index = event.port_index,
+ .buffer =
+ std::string(reinterpret_cast(event.buffer),
+ event.size),
+ };
+ } break;
+ case CLAP_EVENT_MIDI2: {
+ const auto& event =
+ reinterpret_cast(generic_event);
+ payload = payload::Midi2{
+ .port_index = event.port_index,
+ .data{event.data[0], event.data[1], event.data[2],
+ event.data[3]},
+ };
+ } break;
+ }
+ }
+
+ if (payload) {
+ return Event{.time = generic_event.time,
+ .flags = generic_event.flags,
+ .payload = std::move(*payload)};
+ } else {
+ return std::nullopt;
+ }
+}
+
+} // namespace events
+} // namespace clap
diff --git a/src/common/serialization/clap/events.h b/src/common/serialization/clap/events.h
index f93d1bbc..91a396bd 100644
--- a/src/common/serialization/clap/events.h
+++ b/src/common/serialization/clap/events.h
@@ -268,6 +268,12 @@ struct Midi2 {
* reconstructed back to a `clap_event_header`.
*/
struct alignas(16) Event {
+ /**
+ * Parse a CLAP event. Returns a nullopt if yabridge does not support the
+ * event.
+ */
+ static std::optional parse(const clap_event_header_t& generic_event);
+
/**
* The time from the event header.
*/
diff --git a/src/plugin/meson.build b/src/plugin/meson.build
index f8ad36e5..3dfac8a0 100644
--- a/src/plugin/meson.build
+++ b/src/plugin/meson.build
@@ -80,6 +80,7 @@ if with_clap
'../common/serialization/clap/ext/audio-ports.cpp',
'../common/serialization/clap/ext/note-ports.cpp',
'../common/serialization/clap/ext/params.cpp',
+ '../common/serialization/clap/events.cpp',
'../common/serialization/clap/host.cpp',
'../common/serialization/clap/plugin.cpp',
'../common/serialization/clap/stream.cpp',
diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build
index d8f9f550..5926f158 100644
--- a/src/wine-host/meson.build
+++ b/src/wine-host/meson.build
@@ -83,6 +83,7 @@ if with_clap
'../common/serialization/clap/ext/audio-ports.cpp',
'../common/serialization/clap/ext/note-ports.cpp',
'../common/serialization/clap/ext/params.cpp',
+ '../common/serialization/clap/events.cpp',
'../common/serialization/clap/host.cpp',
'../common/serialization/clap/plugin.cpp',
'../common/serialization/clap/stream.cpp',