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/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..261fb18c2 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,18 @@ 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); +# else + (void)ptr; + (void)channel; + (void)note; + (void)velocity; +# 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..b3ceb5231 100644 --- a/distrho/src/DistrhoPluginVST.cpp +++ b/distrho/src/DistrhoPluginVST.cpp @@ -155,13 +155,54 @@ 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), @@ -318,8 +359,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 +383,7 @@ class UIVst // Vst stuff const audioMasterCallback fAudioMaster; AEffect* const fEffect; - ParameterCheckHelper* const fUiHelper; + UIHelperVst* const fUiHelper; PluginExporter* const fPlugin; // Plugin UI @@ -381,7 +426,7 @@ class UIVst // ----------------------------------------------------------------------- -class PluginVst : public ParameterCheckHelper +class PluginVst : public UIHelperVst { public: PluginVst(const audioMasterCallback audioMaster, AEffect* const effect) @@ -542,6 +587,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 +1066,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..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