DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

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:
Mdistrho/src/DistrhoPluginCLAP.cpp | 162++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Adistrho/src/clap/ext/note-ports.h | 78++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adistrho/src/clap/ext/state.h | 33+++++++++++++++++++++++++++++++++
Adistrho/src/clap/ext/timer-support.h | 29+++++++++++++++++++++++++++++
Adistrho/src/clap/stream.h | 26++++++++++++++++++++++++++
Mexamples/SendNote/DistrhoPluginInfo.h | 3++-
Mexamples/SendNote/Makefile | 1+
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)