commit 4d37a8e3011b1d04f71edbff572454b103cbdabf
parent 28ea3aa85208894b5cc3a30fa9035395bcbda060
Author: falkTX <falktx@falktx.com>
Date: Fri, 9 Sep 2022 11:50:49 +0100
Deal with MIDI input for clap
Diffstat:
7 files changed, 321 insertions(+), 11 deletions(-)
diff --git a/distrho/src/DistrhoPluginCLAP.cpp b/distrho/src/DistrhoPluginCLAP.cpp
@@ -19,12 +19,17 @@
#if DISTRHO_PLUGIN_HAS_UI
# include "DistrhoUIInternal.hpp"
-# include "extra/Mutex.hpp"
+# include "../extra/Mutex.hpp"
+#endif
+
+#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+# include "../extra/RingBuffer.hpp"
#endif
#include "clap/entry.h"
#include "clap/plugin-factory.h"
#include "clap/ext/audio-ports.h"
+#include "clap/ext/note-ports.h"
#include "clap/ext/gui.h"
#include "clap/ext/params.h"
@@ -83,6 +88,10 @@ struct ClapEventQueue
} fEventQueue;
#endif
+ #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ SmallStackBuffer fNotesBuffer;
+ #endif
+
#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_STATE
virtual void setStateFromUI(const char* key, const char* value) = 0;
#endif
@@ -116,6 +125,16 @@ struct ClapEventQueue
}
} fCachedParameters;
+ ClapEventQueue()
+ #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ : fNotesBuffer(StackBuffer_INIT)
+ #endif
+ {
+ #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT && ! defined(DISTRHO_PROPER_CPP11_SUPPORT)
+ std::memset(&fNotesBuffer, 0, sizeof(fNotesBuffer));
+ #endif
+ }
+
virtual ~ClapEventQueue() {}
};
@@ -141,13 +160,15 @@ public:
fPluinEventQueue(eventQueue),
fEventQueue(eventQueue->fEventQueue),
fCachedParameters(eventQueue->fCachedParameters),
- fUI(),
fIsFloating(isFloating),
fCallbackRegistered(false),
fScaleFactor(0.0),
fParentWindow(0),
fTransientWindow(0)
{
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ fNotesRingBuffer.setRingBuffer(&eventQueue->fNotesBuffer, false);
+ #endif
}
~ClapUI() override
@@ -360,6 +381,9 @@ private:
ClapEventQueue* const fPluinEventQueue;
ClapEventQueue::Queue& fEventQueue;
ClapEventQueue::CachedParameters& fCachedParameters;
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ RingBufferControl<SmallStackBuffer> fNotesRingBuffer;
+ #endif
ScopedPointer<UIExporter> fUI;
const bool fIsFloating;
@@ -474,6 +498,12 @@ private:
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity)
{
+ uint8_t midiData[3];
+ midiData[0] = (velocity != 0 ? 0x90 : 0x80) | channel;
+ midiData[1] = note;
+ midiData[2] = velocity;
+ fNotesRingBuffer.writeCustomData(midiData, 3);
+ fNotesRingBuffer.commitWrite();
}
static void sendNoteCallback(void* const ptr, const uint8_t channel, const uint8_t note, const uint8_t velocity)
@@ -522,8 +552,14 @@ public:
updateStateValueCallback),
fHost(host),
fOutputEvents(nullptr)
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ , fMidiEventCount(0)
+ #endif
{
fCachedParameters.setup(fPlugin.getParameterCount());
+ #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ fNotesRingBuffer.setRingBuffer(&fNotesBuffer, true);
+ #endif
}
// ----------------------------------------------------------------------------------------------------------------
@@ -552,6 +588,10 @@ public:
bool process(const clap_process_t* const process)
{
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ fMidiEventCount = 0;
+ #endif
+
#if DISTRHO_PLUGIN_HAS_UI
if (const clap_output_events_t* const outputEvents = process->out_events)
{
@@ -697,6 +737,12 @@ public:
case CLAP_EVENT_PARAM_GESTURE_END:
case CLAP_EVENT_TRANSPORT:
case CLAP_EVENT_MIDI:
+ DISTRHO_SAFE_ASSERT_UINT2_BREAK(event->size == sizeof(clap_event_midi_t),
+ event->size, sizeof(clap_event_midi_t));
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ addMidiEvent(static_cast<const clap_event_midi_t*>(static_cast<const void*>(event)));
+ #endif
+ break;
case CLAP_EVENT_MIDI_SYSEX:
case CLAP_EVENT_MIDI2:
break;
@@ -705,6 +751,28 @@ public:
}
}
+ #if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ if (fMidiEventCount != kMaxMidiEvents && fNotesRingBuffer.isDataAvailableForReading())
+ {
+ uint8_t midiData[3];
+ const uint32_t frame = fMidiEventCount != 0 ? fMidiEvents[fMidiEventCount-1].frame : 0;
+
+ while (fNotesRingBuffer.isDataAvailableForReading())
+ {
+ if (! fNotesRingBuffer.readCustomData(midiData, 3))
+ break;
+
+ MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
+ midiEvent.frame = frame;
+ midiEvent.size = 3;
+ std::memcpy(midiEvent.data, midiData, 3);
+
+ if (fMidiEventCount == kMaxMidiEvents)
+ break;
+ }
+ }
+ #endif
+
if (const uint32_t frames = process->frames_count)
{
// TODO multi-port bus stuff
@@ -723,7 +791,7 @@ public:
fOutputEvents = process->out_events;
#if DISTRHO_PLUGIN_WANT_MIDI_INPUT
- fPlugin.run(inputs, outputs, frames, nullptr, 0);
+ fPlugin.run(inputs, outputs, frames, fMidiEvents, fMidiEventCount);
#else
fPlugin.run(inputs, outputs, frames);
#endif
@@ -857,13 +925,6 @@ public:
return true;
}
- void setParameterValueFromEvent(const clap_event_param_value* const param)
- {
- fCachedParameters.values[param->param_id] = param->value;
- fCachedParameters.changed[param->param_id] = true;
- fPlugin.setParameterValue(param->param_id, param->value);
- }
-
void flushParameters(const clap_input_events_t* const in, const clap_output_events_t* const out)
{
if (const uint32_t len = in != nullptr ? in->size(in) : 0)
@@ -911,6 +972,30 @@ public:
}
// ----------------------------------------------------------------------------------------------------------------
+
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ void addMidiEvent(const clap_event_midi_t* const event)
+ {
+ DISTRHO_SAFE_ASSERT_UINT_RETURN(event->port_index == 0, event->port_index,);
+
+ if (fMidiEventCount == kMaxMidiEvents)
+ return;
+
+ MidiEvent& midiEvent(fMidiEvents[fMidiEventCount++]);
+ midiEvent.frame = event->header.time;
+ midiEvent.size = 3;
+ std::memcpy(midiEvent.data, event->data, 3);
+ }
+ #endif
+
+ void setParameterValueFromEvent(const clap_event_param_value* const event)
+ {
+ fCachedParameters.values[event->param_id] = event->value;
+ fCachedParameters.changed[event->param_id] = true;
+ fPlugin.setParameterValue(event->param_id, event->value);
+ }
+
+ // ----------------------------------------------------------------------------------------------------------------
// gui
#if DISTRHO_PLUGIN_HAS_UI
@@ -952,6 +1037,15 @@ private:
// CLAP stuff
const clap_host_t* const fHost;
const clap_output_events_t* fOutputEvents;
+
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ uint32_t fMidiEventCount;
+ MidiEvent fMidiEvents[kMaxMidiEvents];
+ #if DISTRHO_PLUGIN_HAS_UI
+ RingBufferControl<SmallStackBuffer> fNotesRingBuffer;
+ #endif
+ #endif
+
#if DISTRHO_PLUGIN_WANT_TIMEPOS
TimePosition fTimePosition;
#endif
@@ -1209,6 +1303,52 @@ static const clap_plugin_audio_ports_t clap_plugin_audio_ports = {
};
// --------------------------------------------------------------------------------------------------------------------
+// plugin audio ports
+
+static uint32_t clap_plugin_note_ports_count(const clap_plugin_t*, const bool is_input)
+{
+ return (is_input ? DISTRHO_PLUGIN_WANT_MIDI_INPUT : DISTRHO_PLUGIN_WANT_MIDI_OUTPUT) != 0 ? 1 : 0;
+}
+
+static bool clap_plugin_note_ports_get(const clap_plugin_t*, uint32_t, const bool is_input, clap_note_port_info_t* const info)
+{
+ if (is_input)
+ {
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT
+ info->id = 0;
+ info->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
+ info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
+ std::strcpy(info->name, "Event/MIDI Input");
+ return true;
+ #else
+ return false;
+ #endif
+ }
+ else
+ {
+ #if DISTRHO_PLUGIN_WANT_MIDI_OUTPUT
+ info->id = 0;
+ info->supported_dialects = CLAP_NOTE_DIALECT_MIDI;
+ info->preferred_dialect = CLAP_NOTE_DIALECT_MIDI;
+ std::strcpy(info->name, "Event/MIDI Output");
+ return true;
+ #else
+ return false;
+ #endif
+ }
+
+ #if DISTRHO_PLUGIN_WANT_MIDI_INPUT+DISTRHO_PLUGIN_WANT_MIDI_OUTPUT == 0
+ // unused
+ (void)info;
+ #endif
+}
+
+static const clap_plugin_note_ports_t clap_plugin_note_ports = {
+ clap_plugin_note_ports_count,
+ clap_plugin_note_ports_get
+};
+
+// --------------------------------------------------------------------------------------------------------------------
// plugin parameters
static uint32_t clap_plugin_params_count(const clap_plugin_t* const plugin)
@@ -1316,6 +1456,8 @@ static const void* clap_plugin_get_extension(const clap_plugin_t*, const char* c
{
if (std::strcmp(id, CLAP_EXT_AUDIO_PORTS) == 0)
return &clap_plugin_audio_ports;
+ if (std::strcmp(id, CLAP_EXT_NOTE_PORTS) == 0)
+ return &clap_plugin_note_ports;
if (std::strcmp(id, CLAP_EXT_PARAMS) == 0)
return &clap_plugin_params;
#if DISTRHO_PLUGIN_HAS_UI
diff --git a/distrho/src/clap/ext/note-ports.h b/distrho/src/clap/ext/note-ports.h
@@ -0,0 +1,78 @@
+#pragma once
+
+#include "../plugin.h"
+#include "../string-sizes.h"
+
+/// @page Note Ports
+///
+/// This extension provides a way for the plugin to describe its current note ports.
+/// If the plugin does not implement this extension, it won't have note input or output.
+/// The plugin is only allowed to change its note ports configuration while it is deactivated.
+
+static CLAP_CONSTEXPR const char CLAP_EXT_NOTE_PORTS[] = "clap.note-ports";
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum clap_note_dialect {
+ // Uses clap_event_note and clap_event_note_expression.
+ CLAP_NOTE_DIALECT_CLAP = 1 << 0,
+
+ // Uses clap_event_midi, no polyphonic expression
+ CLAP_NOTE_DIALECT_MIDI = 1 << 1,
+
+ // Uses clap_event_midi, with polyphonic expression (MPE)
+ CLAP_NOTE_DIALECT_MIDI_MPE = 1 << 2,
+
+ // Uses clap_event_midi2
+ CLAP_NOTE_DIALECT_MIDI2 = 1 << 3,
+};
+
+typedef struct clap_note_port_info {
+ // id identifies a port and must be stable.
+ // id may overlap between input and output ports.
+ clap_id id;
+ uint32_t supported_dialects; // bitfield, see clap_note_dialect
+ uint32_t preferred_dialect; // one value of clap_note_dialect
+ char name[CLAP_NAME_SIZE]; // displayable name, i18n?
+} clap_note_port_info_t;
+
+// The note ports scan has to be done while the plugin is deactivated.
+typedef struct clap_plugin_note_ports {
+ // number of ports, for either input or output
+ // [main-thread]
+ uint32_t (*count)(const clap_plugin_t *plugin, bool is_input);
+
+ // get info about about a note port.
+ // [main-thread]
+ bool (*get)(const clap_plugin_t *plugin,
+ uint32_t index,
+ bool is_input,
+ clap_note_port_info_t *info);
+} clap_plugin_note_ports_t;
+
+enum {
+ // The ports have changed, the host shall perform a full scan of the ports.
+ // This flag can only be used if the plugin is not active.
+ // If the plugin active, call host->request_restart() and then call rescan()
+ // when the host calls deactivate()
+ CLAP_NOTE_PORTS_RESCAN_ALL = 1 << 0,
+
+ // The ports name did change, the host can scan them right away.
+ CLAP_NOTE_PORTS_RESCAN_NAMES = 1 << 1,
+};
+
+typedef struct clap_host_note_ports {
+ // Query which dialects the host supports
+ // [main-thread]
+ uint32_t (*supported_dialects)(const clap_host_t *host);
+
+ // Rescan the full list of note ports according to the flags.
+ // [main-thread]
+ void (*rescan)(const clap_host_t *host, uint32_t flags);
+} clap_host_note_ports_t;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/distrho/src/clap/ext/state.h b/distrho/src/clap/ext/state.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "../plugin.h"
+#include "../stream.h"
+
+static CLAP_CONSTEXPR const char CLAP_EXT_STATE[] = "clap.state";
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct clap_plugin_state {
+ // Saves the plugin state into stream.
+ // Returns true if the state was correctly saved.
+ // [main-thread]
+ bool (*save)(const clap_plugin_t *plugin, const clap_ostream_t *stream);
+
+ // Loads the plugin state from stream.
+ // Returns true if the state was correctly restored.
+ // [main-thread]
+ bool (*load)(const clap_plugin_t *plugin, const clap_istream_t *stream);
+} clap_plugin_state_t;
+
+typedef struct clap_host_state {
+ // Tell the host that the plugin state has changed and should be saved again.
+ // If a parameter value changes, then it is implicit that the state is dirty.
+ // [main-thread]
+ void (*mark_dirty)(const clap_host_t *host);
+} clap_host_state_t;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/distrho/src/clap/ext/timer-support.h b/distrho/src/clap/ext/timer-support.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include "../plugin.h"
+
+static CLAP_CONSTEXPR const char CLAP_EXT_TIMER_SUPPORT[] = "clap.timer-support";
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct clap_plugin_timer_support {
+ // [main-thread]
+ void (*on_timer)(const clap_plugin_t *plugin, clap_id timer_id);
+} clap_plugin_timer_support_t;
+
+typedef struct clap_host_timer_support {
+ // Registers a periodic timer.
+ // The host may adjust the period if it is under a certain threshold.
+ // 30 Hz should be allowed.
+ // [main-thread]
+ bool (*register_timer)(const clap_host_t *host, uint32_t period_ms, clap_id *timer_id);
+
+ // [main-thread]
+ bool (*unregister_timer)(const clap_host_t *host, clap_id timer_id);
+} clap_host_timer_support_t;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/distrho/src/clap/stream.h b/distrho/src/clap/stream.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "private/std.h"
+#include "private/macros.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct clap_istream {
+ void *ctx; // reserved pointer for the stream
+
+ // returns the number of bytes read; 0 indicates end of file and -1 a read error
+ int64_t (*read)(const struct clap_istream *stream, void *buffer, uint64_t size);
+} clap_istream_t;
+
+typedef struct clap_ostream {
+ void *ctx; // reserved pointer for the stream
+
+ // returns the number of bytes written; -1 on write error
+ int64_t (*write)(const struct clap_ostream *stream, const void *buffer, uint64_t size);
+} clap_ostream_t;
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/examples/SendNote/DistrhoPluginInfo.h b/examples/SendNote/DistrhoPluginInfo.h
@@ -1,6 +1,6 @@
/*
* DISTRHO Plugin Framework (DPF)
- * Copyright (C) 2012-2021 Filipe Coelho <falktx@falktx.com>
+ * Copyright (C) 2012-2022 Filipe Coelho <falktx@falktx.com>
*
* Permission to use, copy, modify, and/or distribute this software for any purpose with
* or without fee is hereby granted, provided that the above copyright notice and this
@@ -24,6 +24,7 @@
#define DISTRHO_PLUGIN_HAS_UI 1
#define DISTRHO_PLUGIN_HAS_EMBED_UI 1
#define DISTRHO_PLUGIN_IS_RT_SAFE 1
+#define DISTRHO_PLUGIN_IS_SYNTH 1
#define DISTRHO_PLUGIN_NUM_INPUTS 0
#define DISTRHO_PLUGIN_NUM_OUTPUTS 1
#define DISTRHO_PLUGIN_WANT_MIDI_INPUT 1
diff --git a/examples/SendNote/Makefile b/examples/SendNote/Makefile
@@ -34,6 +34,7 @@ endif
TARGETS += lv2_sep
TARGETS += vst2
TARGETS += vst3
+TARGETS += clap
all: $(TARGETS)