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..c30ebc7da 100644 --- a/Makefile.plugins.mk +++ b/Makefile.plugins.mk @@ -98,6 +98,10 @@ DGL_FLAGS += -DDGL_EXTERNAL HAVE_DGL = true endif +ifneq ($(UI_TYPE),none) +THREAD_LIBS += -lpthread +endif + DGL_LIBS += $(DGL_SYSTEM_LIBS) ifneq ($(HAVE_DGL),true) @@ -171,7 +175,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 @@ -234,7 +238,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) $(THREAD_LIBS) $(SHARED) -o $@ # --------------------------------------------------------------------------------------------------------------------- diff --git a/distrho/DistrhoUI.hpp b/distrho/DistrhoUI.hpp index ecebc368c..07fed8ca3 100644 --- a/distrho/DistrhoUI.hpp +++ b/distrho/DistrhoUI.hpp @@ -118,6 +118,12 @@ class UI : public UIWidget @note Work in progress. Implemented for DSSI and LV2 formats. */ void sendNote(uint8_t channel, uint8_t note, uint8_t velocity); + + /** + sendMidi. + @TODO Document this. + */ + void sendMidi(const uint8_t* data, uint32_t size); #endif #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS diff --git a/distrho/src/DistrhoPluginCarla.cpp b/distrho/src/DistrhoPluginCarla.cpp index 0bbb3a504..9c81d2b36 100644 --- a/distrho/src/DistrhoPluginCarla.cpp +++ b/distrho/src/DistrhoPluginCarla.cpp @@ -38,7 +38,7 @@ static const writeMidiFunc writeMidiCallback = nullptr; static const setStateFunc setStateCallback = nullptr; #endif #if ! DISTRHO_PLUGIN_IS_SYNTH -static const sendNoteFunc sendNoteCallback = nullptr; +static const sendMidiFunc sendMidiCallback = nullptr; #endif class UICarla @@ -46,7 +46,7 @@ class UICarla public: UICarla(const NativeHostDescriptor* const host, PluginExporter* const plugin) : fHost(host), - fUI(this, 0, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, plugin->getInstancePointer()) + fUI(this, 0, editParameterCallback, setParameterCallback, setStateCallback, sendMidiCallback, setSizeCallback, plugin->getInstancePointer()) { fUI.setWindowTitle(host->uiName); @@ -116,7 +116,7 @@ class UICarla #endif #if DISTRHO_PLUGIN_IS_SYNTH - void handleSendNote(const uint8_t, const uint8_t, const uint8_t) + void handleSendMidi(const uint8_t* const, const uint32_t) { // TODO } @@ -159,9 +159,9 @@ class UICarla #endif #if DISTRHO_PLUGIN_IS_SYNTH - static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) + static void sendMidiCallback(void* ptr, const uint8_t* data, uint32_t size) { - handlePtr->handleSendNote(channel, note, velocity); + handlePtr->handleSendMidi(data, size); } #endif diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp index b05663ef4..b167c37f0 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/DistrhoPluginJack.cpp b/distrho/src/DistrhoPluginJack.cpp index 7cb0684a9..081ca505e 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, sendMidiCallback, 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,19 @@ class PluginJack #endif #if DISTRHO_PLUGIN_HAS_UI +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + void sendMidi(const uint8_t* const data, const uint32_t size) + { + if (size > 3) + return; + + uint8_t midiData[3] = {0, 0, 0}; + memcpy(midiData, data, size); + + fMidiQueue.send(midiData); + } +# endif + void setSize(const uint width, const uint height) { fUI.setWindowSize(width, height); @@ -535,6 +548,9 @@ class PluginJack # if DISTRHO_PLUGIN_WANT_PROGRAMS int fProgramChanged; # endif +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + SimpleMidiQueue fMidiQueue; +# endif #endif // ------------------------------------------------------------------- @@ -578,6 +594,17 @@ class PluginJack #endif #if DISTRHO_PLUGIN_HAS_UI + static void sendMidiCallback(void* ptr, const uint8_t* data, uint32_t size) + { +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + thisPtr->sendMidi(data, size); +# else + (void)ptr; + (void)data; + (void)size; +# endif + } + static void setSizeCallback(void* ptr, uint width, uint height) { thisPtr->setSize(width, height); diff --git a/distrho/src/DistrhoPluginVST.cpp b/distrho/src/DistrhoPluginVST.cpp index bc57b7ecc..ad7e90b7d 100644 --- a/distrho/src/DistrhoPluginVST.cpp +++ b/distrho/src/DistrhoPluginVST.cpp @@ -155,18 +155,59 @@ class ParameterCheckHelper #endif }; +#if DISTRHO_PLUGIN_HAS_UI && DISTRHO_PLUGIN_WANT_MIDI_INPUT +// ----------------------------------------------------------------------- + +class MidiSendFromEditorHelper +{ +public: + virtual ~MidiSendFromEditorHelper() {} + + void clearEditorMidi() + { + fQueue.clear(); + } + + void sendEditorMidi(const uint8_t midiData[3]) + { + fQueue.send(midiData); + } + + uint32_t receiveEditorMidi(MidiEvent* events, uint32_t eventCount) + { + return fQueue.receive(events, eventCount); + } + +private: + SimpleMidiQueue fQueue; +}; +#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), fPlugin(plugin), - fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, scaleFactor, plugin->getInstancePointer()), + fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendMidiCallback, setSizeCallback, scaleFactor, plugin->getInstancePointer()), fShouldCaptureVstKeys(false) { // FIXME only needed for windows? @@ -316,15 +357,20 @@ class UIVst # endif } - void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) + void sendMidi(const uint8_t* const data, const uint32_t size) { -# if 0 //DISTRHO_PLUGIN_WANT_MIDI_INPUT - // TODO +# if DISTRHO_PLUGIN_WANT_MIDI_INPUT + if (size > 3) + return; + + uint8_t midiData[3] = {0, 0, 0}; + memcpy(midiData, data, size); + + fUiHelper->sendEditorMidi(midiData); # else return; // unused - (void)channel; - (void)note; - (void)velocity; + (void)data; + (void)size; # endif } @@ -338,7 +384,7 @@ class UIVst // Vst stuff const audioMasterCallback fAudioMaster; AEffect* const fEffect; - ParameterCheckHelper* const fUiHelper; + UIHelperVst* const fUiHelper; PluginExporter* const fPlugin; // Plugin UI @@ -365,9 +411,9 @@ class UIVst handlePtr->setState(key, value); } - static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) + static void sendMidiCallback(void* ptr, const uint8_t* data, uint32_t size) { - handlePtr->sendNote(channel, note, velocity); + handlePtr->sendMidi(data, size); } static void setSizeCallback(void* ptr, uint width, uint height) @@ -381,7 +427,7 @@ class UIVst // ----------------------------------------------------------------------- -class PluginVst : public ParameterCheckHelper +class PluginVst : public UIHelperVst { public: PluginVst(const audioMasterCallback audioMaster, AEffect* const effect) @@ -542,6 +588,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 +1067,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/distrho/src/DistrhoUI.cpp b/distrho/src/DistrhoUI.cpp index cc0814039..d6a936c26 100644 --- a/distrho/src/DistrhoUI.cpp +++ b/distrho/src/DistrhoUI.cpp @@ -106,7 +106,17 @@ void UI::setState(const char* key, const char* value) #if DISTRHO_PLUGIN_WANT_MIDI_INPUT void UI::sendNote(uint8_t channel, uint8_t note, uint8_t velocity) { - pData->sendNoteCallback(channel, note, velocity); + uint8_t data[3]; + data[0] = 0x90 | channel; + data[1] = note; + data[2] = velocity; + + sendMidi(data, sizeof(data)); +} + +void UI::sendMidi(const uint8_t* data, uint32_t size) +{ + pData->sendMidiCallback(data, size); } #endif diff --git a/distrho/src/DistrhoUIDSSI.cpp b/distrho/src/DistrhoUIDSSI.cpp index b259c6e00..6c40d74b8 100644 --- a/distrho/src/DistrhoUIDSSI.cpp +++ b/distrho/src/DistrhoUIDSSI.cpp @@ -27,7 +27,7 @@ START_NAMESPACE_DISTRHO #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT -static const sendNoteFunc sendNoteCallback = nullptr; +static const sendMidiFunc sendMidiCallback = nullptr; #endif // ----------------------------------------------------------------------- @@ -97,7 +97,7 @@ class UIDssi { public: UIDssi(const OscData& oscData, const char* const uiTitle) - : fUI(this, 0, nullptr, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback), + : fUI(this, 0, nullptr, setParameterCallback, setStateCallback, sendMidiCallback, setSizeCallback), fHostClosed(false), fOscData(oscData) { @@ -188,20 +188,22 @@ class UIDssi } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) + void sendMidi(const uint8_t* const data, const uint32_t size) { - if (fOscData.server == nullptr) + if (size > 3) return; - if (channel > 0xF) + if (fOscData.server == nullptr) return; - uint8_t mdata[4] = { - 0, - static_cast(channel + (velocity != 0 ? 0x90 : 0x80)), - note, - velocity - }; - fOscData.send_midi(mdata); + uint8_t midiBuf[4] = {0, 0, 0, 0}; + memcpy(midiBuf + 1, data, size); + + if ((midiBuf[1] & 0xf0) == 0x90 && midiBuf[3] == 0) + { + midiBuf[1] = 0x80 | (midiBuf[1] & 0x0f); + } + + fOscData.send_midi(midiBuf); } #endif @@ -232,9 +234,9 @@ class UIDssi } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) + static void sendMidiCallback(void* ptr, const uint8_t* data, uint32_t size) { - uiPtr->sendNote(channel, note, velocity); + uiPtr->sendMidi(data, size); } #endif diff --git a/distrho/src/DistrhoUIInternal.hpp b/distrho/src/DistrhoUIInternal.hpp index f6ddde6d0..c2f50e4da 100644 --- a/distrho/src/DistrhoUIInternal.hpp +++ b/distrho/src/DistrhoUIInternal.hpp @@ -51,7 +51,7 @@ extern Window* d_lastUiWindow; typedef void (*editParamFunc) (void* ptr, uint32_t rindex, bool started); typedef void (*setParamFunc) (void* ptr, uint32_t rindex, float value); typedef void (*setStateFunc) (void* ptr, const char* key, const char* value); -typedef void (*sendNoteFunc) (void* ptr, uint8_t channel, uint8_t note, uint8_t velo); +typedef void (*sendMidiFunc) (void* ptr, const uint8_t* data, uint32_t size); typedef void (*setSizeFunc) (void* ptr, uint width, uint height); // ----------------------------------------------------------------------- @@ -76,7 +76,7 @@ struct UI::PrivateData { editParamFunc editParamCallbackFunc; setParamFunc setParamCallbackFunc; setStateFunc setStateCallbackFunc; - sendNoteFunc sendNoteCallbackFunc; + sendMidiFunc sendMidiCallbackFunc; setSizeFunc setSizeCallbackFunc; PrivateData() noexcept @@ -93,7 +93,7 @@ struct UI::PrivateData { editParamCallbackFunc(nullptr), setParamCallbackFunc(nullptr), setStateCallbackFunc(nullptr), - sendNoteCallbackFunc(nullptr), + sendMidiCallbackFunc(nullptr), setSizeCallbackFunc(nullptr) { DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate)); @@ -133,10 +133,10 @@ struct UI::PrivateData { setStateCallbackFunc(callbacksPtr, key, value); } - void sendNoteCallback(const uint8_t channel, const uint8_t note, const uint8_t velocity) + void sendMidiCallback(const uint8_t* data, uint32_t size) { - if (sendNoteCallbackFunc != nullptr) - sendNoteCallbackFunc(callbacksPtr, channel, note, velocity); + if (sendMidiCallbackFunc != nullptr) + sendMidiCallbackFunc(callbacksPtr, data, size); } void setSizeCallback(const uint width, const uint height) @@ -256,7 +256,7 @@ class UIExporter const editParamFunc editParamCall, const setParamFunc setParamCall, const setStateFunc setStateCall, - const sendNoteFunc sendNoteCall, + const sendMidiFunc sendMidiCall, const setSizeFunc setSizeCall, const float scaleFactor = 1.0f, void* const dspPtr = nullptr, @@ -278,7 +278,7 @@ class UIExporter fData->editParamCallbackFunc = editParamCall; fData->setParamCallbackFunc = setParamCall; fData->setStateCallbackFunc = setStateCall; - fData->sendNoteCallbackFunc = sendNoteCall; + fData->sendMidiCallbackFunc = sendMidiCall; fData->setSizeCallbackFunc = setSizeCall; #if !DISTRHO_PLUGIN_HAS_EXTERNAL_UI diff --git a/distrho/src/DistrhoUILV2.cpp b/distrho/src/DistrhoUILV2.cpp index 140909b06..09ae07bff 100644 --- a/distrho/src/DistrhoUILV2.cpp +++ b/distrho/src/DistrhoUILV2.cpp @@ -42,7 +42,7 @@ typedef struct _LV2_Atom_MidiEvent { } LV2_Atom_MidiEvent; #if ! DISTRHO_PLUGIN_WANT_MIDI_INPUT -static const sendNoteFunc sendNoteCallback = nullptr; +static const sendMidiFunc sendMidiCallback = nullptr; #endif // ----------------------------------------------------------------------- @@ -54,7 +54,7 @@ class UiLv2 const LV2_Options_Option* options, const LV2_URID_Map* const uridMap, const LV2UI_Resize* const uiResz, const LV2UI_Touch* uiTouch, const LV2UI_Controller controller, const LV2UI_Write_Function writeFunc, const float scaleFactor, LV2UI_Widget* const widget, void* const dspPtr) - : fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, scaleFactor, dspPtr, bundlePath), + : fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendMidiCallback, setSizeCallback, scaleFactor, dspPtr, bundlePath), fUridMap(uridMap), fUiResize(uiResz), fUiTouch(uiTouch), @@ -268,22 +268,19 @@ class UiLv2 } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - void sendNote(const uint8_t channel, const uint8_t note, const uint8_t velocity) + void sendMidi(const uint8_t* const data, const uint32_t size) { DISTRHO_SAFE_ASSERT_RETURN(fWriteFunction != nullptr,); - if (channel > 0xF) + if (size > 3) return; const uint32_t eventInPortIndex(DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS); LV2_Atom_MidiEvent atomMidiEvent; - atomMidiEvent.atom.size = 3; + atomMidiEvent.atom.size = size; atomMidiEvent.atom.type = fMidiEventURID; - - atomMidiEvent.data[0] = channel + (velocity != 0 ? 0x90 : 0x80); - atomMidiEvent.data[1] = note; - atomMidiEvent.data[2] = velocity; + memcpy(atomMidiEvent.data, data, size); // send to DSP side fWriteFunction(fController, eventInPortIndex, lv2_atom_total_size(&atomMidiEvent.atom), fEventTransferURID, &atomMidiEvent); @@ -339,9 +336,9 @@ class UiLv2 } #if DISTRHO_PLUGIN_WANT_MIDI_INPUT - static void sendNoteCallback(void* ptr, uint8_t channel, uint8_t note, uint8_t velocity) + static void sendMidiCallback(void* ptr, const uint8_t* data, uint32_t size) { - uiPtr->sendNote(channel, note, velocity); + uiPtr->sendMidi(data, size); } #endif 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..7d14c45cb --- /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..e84bcf266 --- /dev/null +++ b/examples/SendNote/SendNoteExampleUI.cpp @@ -0,0 +1,156 @@ +/* + * 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 + +/** + We need the rectangle class from DGL. + */ +using DGL_NAMESPACE::Rectangle; + +// ----------------------------------------------------------------------------------------------------------- + +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