From c3a1aa5b9bb183caf7c60156ba09734070559f02 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 11:46:27 +0100 Subject: [PATCH 1/9] Implement sendNote for VST, and also add a SendNote example --- Makefile | 2 + Makefile.plugins.mk | 8 +- distrho/src/DistrhoPluginVST.cpp | 127 ++++++++++++- examples/SendNote/DistrhoPluginInfo.h | 32 ++++ examples/SendNote/Makefile | 46 +++++ examples/SendNote/README.md | 6 + examples/SendNote/SendNoteExamplePlugin.cpp | 196 ++++++++++++++++++++ examples/SendNote/SendNoteExampleUI.cpp | 151 +++++++++++++++ 8 files changed, 562 insertions(+), 6 deletions(-) create mode 100644 examples/SendNote/DistrhoPluginInfo.h create mode 100644 examples/SendNote/Makefile create mode 100644 examples/SendNote/README.md create mode 100644 examples/SendNote/SendNoteExamplePlugin.cpp create mode 100644 examples/SendNote/SendNoteExampleUI.cpp diff --git a/Makefile b/Makefile index d16647c07..d5577a5f9 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ examples: dgl $(MAKE) all -C examples/Meters $(MAKE) all -C examples/MidiThrough $(MAKE) all -C examples/Parameters + $(MAKE) all -C examples/SendNote $(MAKE) all -C examples/States ifeq ($(HAVE_CAIRO),true) @@ -58,6 +59,7 @@ clean: $(MAKE) clean -C examples/Meters $(MAKE) clean -C examples/MidiThrough $(MAKE) clean -C examples/Parameters + $(MAKE) clean -C examples/SendNote $(MAKE) clean -C examples/States $(MAKE) clean -C utils/lv2-ttl-generator ifneq ($(MACOS_OR_WINDOWS),true) diff --git a/Makefile.plugins.mk b/Makefile.plugins.mk index 7c1e55404..4d69c3f1c 100644 --- a/Makefile.plugins.mk +++ b/Makefile.plugins.mk @@ -98,6 +98,12 @@ DGL_FLAGS += -DDGL_EXTERNAL HAVE_DGL = true endif +ifneq ($(UI_TYPE),none) +ifneq ($(WINDOWS),true) +VST_LIBS += -lpthread +endif +endif + DGL_LIBS += $(DGL_SYSTEM_LIBS) ifneq ($(HAVE_DGL),true) @@ -234,7 +240,7 @@ $(vst): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_VST.cpp.o endif -@mkdir -p $(shell dirname $@) @echo "Creating VST plugin for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(SHARED) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(VST_LIBS) $(SHARED) -o $@ # --------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/DistrhoPluginVST.cpp b/distrho/src/DistrhoPluginVST.cpp index bc57b7ecc..d5175815a 100644 --- a/distrho/src/DistrhoPluginVST.cpp +++ b/distrho/src/DistrhoPluginVST.cpp @@ -23,6 +23,9 @@ #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.hpp" +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT +# include "extra/Mutex.hpp" +# endif #endif #ifndef __cdecl @@ -155,13 +158,116 @@ class ParameterCheckHelper #endif }; +#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT +// ----------------------------------------------------------------------- + +class MidiSendFromEditorHelper +{ +public: + MidiSendFromEditorHelper() + : fMidiStorage(nullptr), + fMidiCount(0), + fWriterIndex(0) + { + fMidiStorage = new ShortMessage[kMidiStorageCapacity]; + } + + virtual ~MidiSendFromEditorHelper() + { + delete[] fMidiStorage; + fMidiStorage = nullptr; + } + + void clearEditorMidi() + { + MutexLocker locker(fMutex); + fMidiCount = 0; + } + + void sendEditorMidi(const uint8_t midiData[3]) + { + MutexLocker locker(fMutex); + + uint32_t count = fMidiCount; + if (count == kMidiStorageCapacity) + return; + + uint32_t index = fWriterIndex; + ShortMessage &msg = fMidiStorage[index]; + std::memcpy(msg.data, midiData, 3); + + fMidiCount = count + 1; + fWriterIndex = (index + 1) % kMidiStorageCapacity; + } + + uint32_t receiveEditorMidi(MidiEvent *events, uint32_t eventCount) + { + if (fMidiCount == 0) + return eventCount; + + MutexTryLocker locker(fMutex); + if (locker.wasNotLocked()) + return eventCount; + + // preserve the ordering of frame times according to messages before us + uint32_t frame = 0; + if (eventCount > 0) + frame = events[eventCount - 1].frame; + + uint32_t countAvailable = fMidiCount; + uint32_t readerIndex = (fWriterIndex + kMidiStorageCapacity - countAvailable) % kMidiStorageCapacity; + for (; countAvailable > 0 && eventCount < kMaxMidiEvents; --countAvailable) + { + ShortMessage msg = fMidiStorage[readerIndex]; + MidiEvent &event = events[eventCount++]; + event.frame = frame; + event.size = 3; + std::memcpy(event.data, msg.data, sizeof(uint8_t)*3); + readerIndex = (readerIndex + 1) % kMaxMidiEvents; + } + + fMidiCount = countAvailable; + return eventCount; + } + +protected: + enum + { + kMidiStorageCapacity = 256, + }; + + struct ShortMessage + { + uint8_t data[3]; + }; + + ShortMessage *fMidiStorage; + volatile uint32_t fMidiCount; + uint32_t fWriterIndex; + Mutex fMutex; +}; +#endif + +// ----------------------------------------------------------------------- + +class UIHelperVst : public ParameterCheckHelper +#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT + , public MidiSendFromEditorHelper +#endif +{ +public: + virtual ~UIHelperVst() + { + } +}; + #if DISTRHO_PLUGIN_HAS_UI // ----------------------------------------------------------------------- class UIVst { public: - UIVst(const audioMasterCallback audioMaster, AEffect* const effect, ParameterCheckHelper* const uiHelper, PluginExporter* const plugin, const intptr_t winId, const float scaleFactor) + UIVst(const audioMasterCallback audioMaster, AEffect* const effect, UIHelperVst* const uiHelper, PluginExporter* const plugin, const intptr_t winId, const float scaleFactor) : fAudioMaster(audioMaster), fEffect(effect), fUiHelper(uiHelper), @@ -318,8 +424,12 @@ class UIVst void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) { -# if 0 //DISTRHO_PLUGIN_WANT_MIDI_INPUT - // TODO +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + uint8_t midiData[3]; + midiData[0] = 0x90 | channel; + midiData[1] = note; + midiData[2] = velocity; + fUiHelper->sendEditorMidi(midiData); # else return; // unused (void)channel; @@ -338,7 +448,7 @@ class UIVst // Vst stuff const audioMasterCallback fAudioMaster; AEffect* const fEffect; - ParameterCheckHelper* const fUiHelper; + UIHelperVst* const fUiHelper; PluginExporter* const fPlugin; // Plugin UI @@ -381,7 +491,7 @@ class UIVst // ----------------------------------------------------------------------- -class PluginVst : public ParameterCheckHelper +class PluginVst : public UIHelperVst { public: PluginVst(const audioMasterCallback audioMaster, AEffect* const effect) @@ -542,6 +652,10 @@ class PluginVst : public ParameterCheckHelper #if DISTRHO_PLUGIN_WANT_MIDI_INPUT fMidiEventCount = 0; +#if DISTRHO_PLUGIN_HAS_UI + clearEditorMidi(); +#endif + // tell host we want MIDI events hostCallback(audioMasterWantMidi); #endif @@ -1017,6 +1131,9 @@ class PluginVst : public ParameterCheckHelper #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT +#if DISTRHO_PLUGIN_HAS_UI + fMidiEventCount = receiveEditorMidi(fMidiEvents, fMidiEventCount); +#endif fPlugin.run(inputs, outputs, sampleFrames, fMidiEvents, fMidiEventCount); fMidiEventCount = 0; #else diff --git a/examples/SendNote/DistrhoPluginInfo.h b/examples/SendNote/DistrhoPluginInfo.h new file mode 100644 index 000000000..6bde8a158 --- /dev/null +++ b/examples/SendNote/DistrhoPluginInfo.h @@ -0,0 +1,32 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2018 Filipe Coelho + * + * 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 + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DISTRHO_PLUGIN_INFO_H_INCLUDED +#define DISTRHO_PLUGIN_INFO_H_INCLUDED + +#define DISTRHO_PLUGIN_BRAND "DISTRHO" +#define DISTRHO_PLUGIN_NAME "SendNote" +#define DISTRHO_PLUGIN_URI "http://distrho.sf.net/examples/SendNote" + +#define DISTRHO_PLUGIN_HAS_UI 1 +#define DISTRHO_PLUGIN_HAS_EMBED_UI 1 +#define DISTRHO_PLUGIN_IS_RT_SAFE 1 +#define DISTRHO_PLUGIN_NUM_INPUTS 0 +#define DISTRHO_PLUGIN_NUM_OUTPUTS 2 +#define DISTRHO_PLUGIN_WANT_MIDI_INPUT 1 +#define DISTRHO_PLUGIN_WANT_MIDI_OUTPUT 0 + +#endif // DISTRHO_PLUGIN_INFO_H_INCLUDED diff --git a/examples/SendNote/Makefile b/examples/SendNote/Makefile new file mode 100644 index 000000000..f10bef32b --- /dev/null +++ b/examples/SendNote/Makefile @@ -0,0 +1,46 @@ +#!/usr/bin/make -f +# Makefile for DISTRHO Plugins # +# ---------------------------- # +# Created by falkTX +# + +# -------------------------------------------------------------- +# Project name, used for binaries + +NAME = d_sendNote + +# -------------------------------------------------------------- +# Files to build + +FILES_DSP = \ + SendNoteExamplePlugin.cpp + + +FILES_UI = \ + SendNoteExampleUI.cpp + +# -------------------------------------------------------------- +# Do some magic + +include ../../Makefile.plugins.mk + +# -------------------------------------------------------------- +# Enable all possible plugin types + +ifeq ($(HAVE_JACK),true) +ifeq ($(HAVE_OPENGL),true) +TARGETS += jack +endif +endif + +ifeq ($(HAVE_OPENGL),true) +TARGETS += lv2_sep +else +TARGETS += lv2_dsp +endif + +TARGETS += vst + +all: $(TARGETS) + +# -------------------------------------------------------------- diff --git a/examples/SendNote/README.md b/examples/SendNote/README.md new file mode 100644 index 000000000..e56ee4e8f --- /dev/null +++ b/examples/SendNote/README.md @@ -0,0 +1,6 @@ +# SendNote example + +This example will show how to send MIDI notes in DPF based UIs.
+ +The UI presents a row of MIDI keys which transmit note events to a synthesizer. + diff --git a/examples/SendNote/SendNoteExamplePlugin.cpp b/examples/SendNote/SendNoteExamplePlugin.cpp new file mode 100644 index 000000000..6727febca --- /dev/null +++ b/examples/SendNote/SendNoteExamplePlugin.cpp @@ -0,0 +1,196 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2018 Filipe Coelho + * + * 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 + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "DistrhoPlugin.hpp" + +#include +#include + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +/** + Plugin that demonstrates sending notes from the editor in DPF. + */ +class SendNoteExamplePlugin : public Plugin +{ +public: + SendNoteExamplePlugin() + : Plugin(0, 0, 0) + { + std::memset(fNotesPlayed, 0, sizeof(fNotesPlayed)); + std::memset(fOscillatorPhases, 0, sizeof(fOscillatorPhases)); + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * Information */ + + /** + Get the plugin label. + This label is a short restricted name consisting of only _, a-z, A-Z and 0-9 characters. + */ + const char* getLabel() const override + { + return "SendNote"; + } + + /** + Get an extensive comment/description about the plugin. + */ + const char* getDescription() const override + { + return "Plugin that demonstrates sending notes from the editor in DPF."; + } + + /** + Get the plugin author/maker. + */ + const char* getMaker() const override + { + return "DISTRHO"; + } + + /** + Get the plugin homepage. + */ + const char* getHomePage() const override + { + return "https://github.com/DISTRHO/DPF"; + } + + /** + Get the plugin license name (a single line of text). + For commercial plugins this should return some short copyright information. + */ + const char* getLicense() const override + { + return "ISC"; + } + + /** + Get the plugin version, in hexadecimal. + */ + uint32_t getVersion() const override + { + return d_version(1, 0, 0); + } + + /** + Get the plugin unique Id. + This value is used by LADSPA, DSSI and VST plugin formats. + */ + int64_t getUniqueId() const override + { + return d_cconst('d', 'S', 'N', 'o'); + } + + /* -------------------------------------------------------------------------------------------------------- + * Init and Internal data, unused in this plugin */ + + void initParameter(uint32_t, Parameter&) override {} + float getParameterValue(uint32_t) const override { return 0.0f;} + void setParameterValue(uint32_t, float) override {} + + /* -------------------------------------------------------------------------------------------------------- + * Audio/MIDI Processing */ + + /** + Run/process function for plugins with MIDI input. + This synthesizes the MIDI voices with a sum of sine waves. + */ + void run(const float**, float** outputs, uint32_t frames, + const MidiEvent* midiEvents, uint32_t midiEventCount) override + { + for (uint32_t i = 0; i < midiEventCount; ++i) + { + if (midiEvents[i].size <= 3) + { + uint8_t status = midiEvents[i].data[0]; + uint8_t note = midiEvents[i].data[1] & 127; + uint8_t velocity = midiEvents[i].data[2] & 127; + + switch (status & 0xf0) + { + case 0x90: + if (velocity != 0) + { + fNotesPlayed[note] = velocity; + break; + } + /* fall through */ + case 0x80: + fNotesPlayed[note] = 0; + fOscillatorPhases[note] = 0; + break; + } + } + } + + float *outputLeft = outputs[0]; + float *outputRight = outputs[1]; + + std::memset(outputLeft, 0, frames * sizeof(float)); + + for (uint32_t noteNumber = 0; noteNumber < 128; ++noteNumber) + { + if (fNotesPlayed[noteNumber] == 0) + continue; + + float notePitch = 8.17579891564 * std::exp(0.0577622650 * noteNumber); + + float phase = fOscillatorPhases[noteNumber]; + float timeStep = notePitch / getSampleRate(); + float k2pi = 2.0 * M_PI; + float gain = 0.1; + + for (uint32_t i = 0; i < frames; ++i) + { + outputLeft[i] += gain * std::sin(k2pi * phase); + phase += timeStep; + phase -= (int)phase; + } + + fOscillatorPhases[noteNumber] = phase; + } + + std::memcpy(outputRight, outputLeft, frames * sizeof(float)); + } + + // ------------------------------------------------------------------------------------------------------- + +private: + uint8_t fNotesPlayed[128]; + float fOscillatorPhases[128]; + + /** + Set our plugin class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SendNoteExamplePlugin) +}; + +/* ------------------------------------------------------------------------------------------------------------ + * Plugin entry point, called by DPF to create a new plugin instance. */ + +Plugin* createPlugin() +{ + return new SendNoteExamplePlugin(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO diff --git a/examples/SendNote/SendNoteExampleUI.cpp b/examples/SendNote/SendNoteExampleUI.cpp new file mode 100644 index 000000000..0dbadffee --- /dev/null +++ b/examples/SendNote/SendNoteExampleUI.cpp @@ -0,0 +1,151 @@ +/* + * DISTRHO Plugin Framework (DPF) + * Copyright (C) 2012-2019 Filipe Coelho + * + * 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 + * permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD + * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN + * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "DistrhoPluginInfo.h" + +#include "DistrhoUI.hpp" + +#include "Window.hpp" + +#include + +START_NAMESPACE_DISTRHO + +// ----------------------------------------------------------------------------------------------------------- + +class SendNoteExampleUI : public UI +{ +public: + SendNoteExampleUI() + : UI(64*12+8, 64+8) + { + std::memset(fKeyState, 0, sizeof(fKeyState)); + } + +protected: + /* -------------------------------------------------------------------------------------------------------- + * DSP/Plugin Callbacks */ + + /** + A parameter has changed on the plugin side. + This is called by the host to inform the UI about parameter changes. + */ + void parameterChanged(uint32_t index, float value) override + { + (void)index; + (void)value; + } + + /* -------------------------------------------------------------------------------------------------------- + * Widget Callbacks */ + + /** + The OpenGL drawing function. + This UI will draw a row of 12 keys, with on/off states according to pressed status. + */ + void onDisplay() override + { + for (int key = 0; key < 12; ++key) + { + bool pressed = fKeyState[key]; + Rectangle bounds = getKeyBounds(key); + + if (pressed) + glColor3f(0.8f, 0.5f, 0.3f); + else + glColor3f(0.3f, 0.5f, 0.8f); + + bounds.draw(); + } + } + + /** + Mouse press event. + This UI will de/activate keys when you click them and reports it as MIDI note events to the plugin. + */ + bool onMouse(const MouseEvent& ev) override + { + // Test for left-clicked + pressed first. + if (ev.button != 1 || ! ev.press) + return false; + + // Find the key which is pressed, if any + int whichKey = -1; + for (int key = 0; key < 12 && whichKey == -1; ++key) + { + Rectangle bounds = getKeyBounds(key); + + if (bounds.contains(ev.pos)) + whichKey = key; + } + + if (whichKey == -1) + return false; + + // Send a note event. Velocity=0 means off + bool pressed = !fKeyState[whichKey]; + sendNote(0, kNoteOctaveStart+whichKey, pressed ? kNoteVelocity : 0); + + // Invert the pressed state of this key, and update display + fKeyState[whichKey] = pressed; + repaint(); + + return true; + } + + /** + Set our UI class as non-copyable and add a leak detector just in case. + */ + DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SendNoteExampleUI) + +private: + /** + Get the bounds of a particular key of the virtual MIDI keyboard. + */ + Rectangle getKeyBounds(unsigned index) const + { + Rectangle bounds; + int padding = 8; + bounds.setX(64 * index + padding); + bounds.setY(padding); + bounds.setWidth(64 - padding); + bounds.setHeight(64 - padding); + return bounds; + } + + /** + The pressed state of one octave of a virtual MIDI keyboard. + */ + bool fKeyState[12]; + + enum + { + kNoteVelocity = 100, // velocity of sent Note-On events + kNoteOctaveStart = 60, // starting note of the virtual MIDI keyboard + }; +}; + +/* ------------------------------------------------------------------------------------------------------------ + * UI entry point, called by DPF to create a new UI instance. */ + +UI* createUI() +{ + return new SendNoteExampleUI(); +} + +// ----------------------------------------------------------------------------------------------------------- + +END_NAMESPACE_DISTRHO From 4669c671860831e91fcb667005d6b06ba58f6fef Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 12:53:20 +0100 Subject: [PATCH 2/9] Make a few things const --- distrho/src/DistrhoPluginVST.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/distrho/src/DistrhoPluginVST.cpp b/distrho/src/DistrhoPluginVST.cpp index d5175815a..eb22455bf 100644 --- a/distrho/src/DistrhoPluginVST.cpp +++ b/distrho/src/DistrhoPluginVST.cpp @@ -180,13 +180,13 @@ class MidiSendFromEditorHelper void clearEditorMidi() { - MutexLocker locker(fMutex); + const MutexLocker locker(fMutex); fMidiCount = 0; } void sendEditorMidi(const uint8_t midiData[3]) { - MutexLocker locker(fMutex); + const MutexLocker locker(fMutex); uint32_t count = fMidiCount; if (count == kMidiStorageCapacity) @@ -205,7 +205,7 @@ class MidiSendFromEditorHelper if (fMidiCount == 0) return eventCount; - MutexTryLocker locker(fMutex); + const MutexTryLocker locker(fMutex); if (locker.wasNotLocked()) return eventCount; From f904fee84bf440816107fb527f8f5a792ca86cfb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 15:47:00 +0100 Subject: [PATCH 3/9] Move the MIDI queue outside of VST --- distrho/src/DistrhoPluginInternal.hpp | 100 ++++++++++++++++++++++++++ distrho/src/DistrhoPluginVST.cpp | 77 ++------------------ 2 files changed, 106 insertions(+), 71 deletions(-) diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp index b05663ef4..076386418 100644 --- a/distrho/src/DistrhoPluginInternal.hpp +++ b/distrho/src/DistrhoPluginInternal.hpp @@ -19,6 +19,10 @@ #include "../DistrhoPlugin.hpp" +#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT +# include "extra/Mutex.hpp" +#endif + START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- @@ -672,6 +676,102 @@ class PluginExporter DISTRHO_PREVENT_HEAP_ALLOCATION }; +#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT +// ----------------------------------------------------------------------- +// Midi queue class + +/** + Single-consumer, single-producer FIFO queue of short MIDI messages, intended + for UI-to-DSP messaging in case of VST or similar plugin formats. + The access is guarded by mutex, using try-lock on the receiving side. + */ +class SimpleMidiQueue +{ +public: + SimpleMidiQueue() + : fMidiStorage(nullptr), + fMidiCount(0), + fWriterIndex(0) + { + fMidiStorage = new ShortMessage[kMidiStorageCapacity]; + } + + virtual ~SimpleMidiQueue() + { + delete[] fMidiStorage; + fMidiStorage = nullptr; + } + + void clear() + { + const MutexLocker locker(fMutex); + fMidiCount = 0; + } + + void send(const uint8_t midiData[3]) + { + const MutexLocker locker(fMutex); + + uint32_t count = fMidiCount; + if (count == kMidiStorageCapacity) + return; + + uint32_t index = fWriterIndex; + ShortMessage &msg = fMidiStorage[index]; + std::memcpy(msg.data, midiData, 3); + + fMidiCount = count + 1; + fWriterIndex = (index + 1) % kMidiStorageCapacity; + } + + uint32_t receive(MidiEvent *events, uint32_t eventCount) + { + if (fMidiCount == 0) + return eventCount; + + const MutexTryLocker locker(fMutex); + if (locker.wasNotLocked()) + return eventCount; + + // preserve the ordering of frame times according to messages before us + uint32_t frame = 0; + if (eventCount > 0) + frame = events[eventCount - 1].frame; + + uint32_t countAvailable = fMidiCount; + uint32_t readerIndex = (fWriterIndex + kMidiStorageCapacity - countAvailable) % kMidiStorageCapacity; + for (; countAvailable > 0 && eventCount < kMaxMidiEvents; --countAvailable) + { + ShortMessage msg = fMidiStorage[readerIndex]; + MidiEvent &event = events[eventCount++]; + event.frame = frame; + event.size = 3; + std::memcpy(event.data, msg.data, sizeof(uint8_t)*3); + readerIndex = (readerIndex + 1) % kMaxMidiEvents; + } + + fMidiCount = countAvailable; + return eventCount; + } + +protected: + enum + { + kMidiStorageCapacity = 256, + }; + + struct ShortMessage + { + uint8_t data[3]; + }; + + ShortMessage *fMidiStorage; + volatile uint32_t fMidiCount; + uint32_t fWriterIndex; + Mutex fMutex; +}; +#endif + // ----------------------------------------------------------------------- END_NAMESPACE_DISTRHO diff --git a/distrho/src/DistrhoPluginVST.cpp b/distrho/src/DistrhoPluginVST.cpp index eb22455bf..109b386b0 100644 --- a/distrho/src/DistrhoPluginVST.cpp +++ b/distrho/src/DistrhoPluginVST.cpp @@ -23,9 +23,6 @@ #if DISTRHO_PLUGIN_HAS_UI # include "DistrhoUIInternal.hpp" -# if DISTRHO_PLUGIN_WANT_MIDI_INPUT -# include "extra/Mutex.hpp" -# endif #endif #ifndef __cdecl @@ -164,87 +161,25 @@ class ParameterCheckHelper class MidiSendFromEditorHelper { public: - MidiSendFromEditorHelper() - : fMidiStorage(nullptr), - fMidiCount(0), - fWriterIndex(0) - { - fMidiStorage = new ShortMessage[kMidiStorageCapacity]; - } - - virtual ~MidiSendFromEditorHelper() - { - delete[] fMidiStorage; - fMidiStorage = nullptr; - } + virtual ~MidiSendFromEditorHelper() {} void clearEditorMidi() { - const MutexLocker locker(fMutex); - fMidiCount = 0; + fQueue.clear(); } void sendEditorMidi(const uint8_t midiData[3]) { - const MutexLocker locker(fMutex); - - uint32_t count = fMidiCount; - if (count == kMidiStorageCapacity) - return; - - uint32_t index = fWriterIndex; - ShortMessage &msg = fMidiStorage[index]; - std::memcpy(msg.data, midiData, 3); - - fMidiCount = count + 1; - fWriterIndex = (index + 1) % kMidiStorageCapacity; + fQueue.send(midiData); } uint32_t receiveEditorMidi(MidiEvent *events, uint32_t eventCount) { - if (fMidiCount == 0) - return eventCount; - - const MutexTryLocker locker(fMutex); - if (locker.wasNotLocked()) - return eventCount; - - // preserve the ordering of frame times according to messages before us - uint32_t frame = 0; - if (eventCount > 0) - frame = events[eventCount - 1].frame; - - uint32_t countAvailable = fMidiCount; - uint32_t readerIndex = (fWriterIndex + kMidiStorageCapacity - countAvailable) % kMidiStorageCapacity; - for (; countAvailable > 0 && eventCount < kMaxMidiEvents; --countAvailable) - { - ShortMessage msg = fMidiStorage[readerIndex]; - MidiEvent &event = events[eventCount++]; - event.frame = frame; - event.size = 3; - std::memcpy(event.data, msg.data, sizeof(uint8_t)*3); - readerIndex = (readerIndex + 1) % kMaxMidiEvents; - } - - fMidiCount = countAvailable; - return eventCount; + return fQueue.receive(events, eventCount); } -protected: - enum - { - kMidiStorageCapacity = 256, - }; - - struct ShortMessage - { - uint8_t data[3]; - }; - - ShortMessage *fMidiStorage; - volatile uint32_t fMidiCount; - uint32_t fWriterIndex; - Mutex fMutex; +private: + SimpleMidiQueue fQueue; }; #endif From 1adbf81544866cc97bf88b63135bc872ba3b4e89 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 16:38:10 +0100 Subject: [PATCH 4/9] Fix the coding style where necessary --- distrho/src/DistrhoPluginInternal.hpp | 4 ++-- distrho/src/DistrhoPluginVST.cpp | 2 +- examples/SendNote/SendNoteExamplePlugin.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp index 076386418..b167c37f0 100644 --- a/distrho/src/DistrhoPluginInternal.hpp +++ b/distrho/src/DistrhoPluginInternal.hpp @@ -724,7 +724,7 @@ class SimpleMidiQueue fWriterIndex = (index + 1) % kMidiStorageCapacity; } - uint32_t receive(MidiEvent *events, uint32_t eventCount) + uint32_t receive(MidiEvent* events, uint32_t eventCount) { if (fMidiCount == 0) return eventCount; @@ -765,7 +765,7 @@ class SimpleMidiQueue uint8_t data[3]; }; - ShortMessage *fMidiStorage; + ShortMessage* fMidiStorage; volatile uint32_t fMidiCount; uint32_t fWriterIndex; Mutex fMutex; diff --git a/distrho/src/DistrhoPluginVST.cpp b/distrho/src/DistrhoPluginVST.cpp index 109b386b0..b3ceb5231 100644 --- a/distrho/src/DistrhoPluginVST.cpp +++ b/distrho/src/DistrhoPluginVST.cpp @@ -173,7 +173,7 @@ class MidiSendFromEditorHelper fQueue.send(midiData); } - uint32_t receiveEditorMidi(MidiEvent *events, uint32_t eventCount) + uint32_t receiveEditorMidi(MidiEvent* events, uint32_t eventCount) { return fQueue.receive(events, eventCount); } diff --git a/examples/SendNote/SendNoteExamplePlugin.cpp b/examples/SendNote/SendNoteExamplePlugin.cpp index 6727febca..7d14c45cb 100644 --- a/examples/SendNote/SendNoteExamplePlugin.cpp +++ b/examples/SendNote/SendNoteExamplePlugin.cpp @@ -141,8 +141,8 @@ class SendNoteExamplePlugin : public Plugin } } - float *outputLeft = outputs[0]; - float *outputRight = outputs[1]; + float* outputLeft = outputs[0]; + float* outputRight = outputs[1]; std::memset(outputLeft, 0, frames * sizeof(float)); From 2aa84642a05e774600388bd7c7ecb6bf2b6ced81 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 17:56:45 +0100 Subject: [PATCH 5/9] Allow it to build when namespace DGL is not used --- examples/SendNote/SendNoteExampleUI.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/SendNote/SendNoteExampleUI.cpp b/examples/SendNote/SendNoteExampleUI.cpp index 0dbadffee..ae43ff6d6 100644 --- a/examples/SendNote/SendNoteExampleUI.cpp +++ b/examples/SendNote/SendNoteExampleUI.cpp @@ -61,7 +61,7 @@ class SendNoteExampleUI : public UI for (int key = 0; key < 12; ++key) { bool pressed = fKeyState[key]; - Rectangle bounds = getKeyBounds(key); + DGL::Rectangle bounds = getKeyBounds(key); if (pressed) glColor3f(0.8f, 0.5f, 0.3f); @@ -86,7 +86,7 @@ class SendNoteExampleUI : public UI int whichKey = -1; for (int key = 0; key < 12 && whichKey == -1; ++key) { - Rectangle bounds = getKeyBounds(key); + DGL::Rectangle bounds = getKeyBounds(key); if (bounds.contains(ev.pos)) whichKey = key; @@ -115,9 +115,9 @@ class SendNoteExampleUI : public UI /** Get the bounds of a particular key of the virtual MIDI keyboard. */ - Rectangle getKeyBounds(unsigned index) const + DGL::Rectangle getKeyBounds(unsigned index) const { - Rectangle bounds; + DGL::Rectangle bounds; int padding = 8; bounds.setX(64 * index + padding); bounds.setY(padding); From 087d7bec05f06af8d41084ebc49bf4fe5848afcd Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 18:24:32 +0100 Subject: [PATCH 6/9] Implement sendNote for JACK --- Makefile.plugins.mk | 6 +-- distrho/src/DistrhoPluginJack.cpp | 61 +++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 23 deletions(-) diff --git a/Makefile.plugins.mk b/Makefile.plugins.mk index 4d69c3f1c..d15418d8f 100644 --- a/Makefile.plugins.mk +++ b/Makefile.plugins.mk @@ -100,7 +100,7 @@ endif ifneq ($(UI_TYPE),none) ifneq ($(WINDOWS),true) -VST_LIBS += -lpthread +THREAD_LIBS += -lpthread endif endif @@ -177,7 +177,7 @@ $(jack): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_JACK.cpp.o endif -@mkdir -p $(shell dirname $@) @echo "Creating JACK standalone for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(shell $(PKG_CONFIG) --libs jack) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(THREAD_LIBS) $(shell $(PKG_CONFIG) --libs jack) -o $@ # --------------------------------------------------------------------------------------------------------------------- # LADSPA @@ -240,7 +240,7 @@ $(vst): $(OBJS_DSP) $(BUILD_DIR)/DistrhoPluginMain_VST.cpp.o endif -@mkdir -p $(shell dirname $@) @echo "Creating VST plugin for $(NAME)" - $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(VST_LIBS) $(SHARED) -o $@ + $(SILENT)$(CXX) $^ $(BUILD_CXX_FLAGS) $(LINK_FLAGS) $(DGL_LIBS) $(THREAD_LIBS) $(SHARED) -o $@ # --------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/src/DistrhoPluginJack.cpp b/distrho/src/DistrhoPluginJack.cpp index 7cb0684a9..e1fa0c642 100644 --- a/distrho/src/DistrhoPluginJack.cpp +++ b/distrho/src/DistrhoPluginJack.cpp @@ -101,7 +101,7 @@ class PluginJack PluginJack(jack_client_t* const client) : fPlugin(this, writeMidiCallback), #if DISTRHO_PLUGIN_HAS_UI - fUI(this, 0, nullptr, setParameterValueCallback, setStateCallback, nullptr, setSizeCallback, getDesktopScaleFactor(), fPlugin.getInstancePointer()), + fUI(this, 0, nullptr, setParameterValueCallback, setStateCallback, sendNoteCallback, setSizeCallback, getDesktopScaleFactor(), fPlugin.getInstancePointer()), #endif fClient(client) { @@ -356,12 +356,13 @@ class PluginJack jack_midi_clear_buffer(fPortMidiOutBuffer); #endif - if (const uint32_t eventCount = jack_midi_get_event_count(midiBuf)) - { #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - uint32_t midiEventCount = 0; - MidiEvent midiEvents[eventCount]; + uint32_t midiEventCount = 0; + MidiEvent midiEvents[kMaxMidiEvents]; #endif + + if (const uint32_t eventCount = jack_midi_get_event_count(midiBuf)) + { jack_midi_event_t jevent; for (uint32_t i=0; i < eventCount; ++i) @@ -410,27 +411,26 @@ class PluginJack #endif #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - MidiEvent& midiEvent(midiEvents[midiEventCount++]); + if (midiEventCount < kMaxMidiEvents) + { + MidiEvent& midiEvent(midiEvents[midiEventCount++]); - midiEvent.frame = jevent.time; - midiEvent.size = jevent.size; + midiEvent.frame = jevent.time; + midiEvent.size = jevent.size; - if (midiEvent.size > MidiEvent::kDataSize) - midiEvent.dataExt = jevent.buffer; - else - std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size); + if (midiEvent.size > MidiEvent::kDataSize) + midiEvent.dataExt = jevent.buffer; + else + std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size); + } #endif } - -#if DISTRHO_PLUGIN_WANT_MIDI_INPUT - fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount); -#endif } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - else - { - fPlugin.run(audioIns, audioOuts, nframes, nullptr, 0); - } +# if DISTRHO_PLUGIN_HAS_UI + midiEventCount = fMidiQueue.receive(midiEvents, midiEventCount); +# endif + fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount); #else fPlugin.run(audioIns, audioOuts, nframes); #endif @@ -466,6 +466,17 @@ class PluginJack #endif #if DISTRHO_PLUGIN_HAS_UI +# 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] = 0x90 | channel; + midiData[1] = note; + midiData[2] = velocity; + fMidiQueue.send(midiData); + } +# endif + void setSize(const uint width, const uint height) { fUI.setWindowSize(width, height); @@ -535,6 +546,9 @@ class PluginJack # if DISTRHO_PLUGIN_WANT_PROGRAMS int fProgramChanged; # endif +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + SimpleMidiQueue fMidiQueue; +# endif #endif // ------------------------------------------------------------------- @@ -578,6 +592,13 @@ class PluginJack #endif #if DISTRHO_PLUGIN_HAS_UI + static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) + { +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + thisPtr->sendNote(channel, note, velocity); +#endif + } + static void setSizeCallback(void* ptr, uint width, uint height) { thisPtr->setSize(width, height); From 8e637c64cb46e3327c7a57caef22941b7a4a6b84 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 18:30:02 +0100 Subject: [PATCH 7/9] Clean up unused variables --- distrho/src/DistrhoPluginJack.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/distrho/src/DistrhoPluginJack.cpp b/distrho/src/DistrhoPluginJack.cpp index e1fa0c642..261fb18c2 100644 --- a/distrho/src/DistrhoPluginJack.cpp +++ b/distrho/src/DistrhoPluginJack.cpp @@ -596,7 +596,12 @@ class PluginJack { # if DISTRHO_PLUGIN_WANT_MIDI_INPUT thisPtr->sendNote(channel, note, velocity); -#endif +# else + (void)ptr; + (void)channel; + (void)note; + (void)velocity; +# endif } static void setSizeCallback(void* ptr, uint width, uint height) From 1a9839e9344d91de5bce0ab69f1ed8a7d99af9c2 Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 18:30:17 +0100 Subject: [PATCH 8/9] Require pthread also on Windows --- Makefile.plugins.mk | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile.plugins.mk b/Makefile.plugins.mk index d15418d8f..c30ebc7da 100644 --- a/Makefile.plugins.mk +++ b/Makefile.plugins.mk @@ -99,10 +99,8 @@ HAVE_DGL = true endif ifneq ($(UI_TYPE),none) -ifneq ($(WINDOWS),true) THREAD_LIBS += -lpthread endif -endif DGL_LIBS += $(DGL_SYSTEM_LIBS) From c7de707f7f287e0a303e91af6a22a7113f89adcb Mon Sep 17 00:00:00 2001 From: Jean Pierre Cimalando Date: Wed, 8 Jan 2020 18:49:31 +0100 Subject: [PATCH 9/9] Fixing the reference to DGL Rectangle, take 2 --- examples/SendNote/SendNoteExampleUI.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/SendNote/SendNoteExampleUI.cpp b/examples/SendNote/SendNoteExampleUI.cpp index ae43ff6d6..e84bcf266 100644 --- a/examples/SendNote/SendNoteExampleUI.cpp +++ b/examples/SendNote/SendNoteExampleUI.cpp @@ -24,6 +24,11 @@ START_NAMESPACE_DISTRHO +/** + We need the rectangle class from DGL. + */ +using DGL_NAMESPACE::Rectangle; + // ----------------------------------------------------------------------------------------------------------- class SendNoteExampleUI : public UI @@ -61,7 +66,7 @@ class SendNoteExampleUI : public UI for (int key = 0; key < 12; ++key) { bool pressed = fKeyState[key]; - DGL::Rectangle bounds = getKeyBounds(key); + Rectangle bounds = getKeyBounds(key); if (pressed) glColor3f(0.8f, 0.5f, 0.3f); @@ -86,7 +91,7 @@ class SendNoteExampleUI : public UI int whichKey = -1; for (int key = 0; key < 12 && whichKey == -1; ++key) { - DGL::Rectangle bounds = getKeyBounds(key); + Rectangle bounds = getKeyBounds(key); if (bounds.contains(ev.pos)) whichKey = key; @@ -115,9 +120,9 @@ class SendNoteExampleUI : public UI /** Get the bounds of a particular key of the virtual MIDI keyboard. */ - DGL::Rectangle getKeyBounds(unsigned index) const + Rectangle getKeyBounds(unsigned index) const { - DGL::Rectangle bounds; + Rectangle bounds; int padding = 8; bounds.setX(64 * index + padding); bounds.setY(padding);