From fe9e89e26085b973edd6edc1f6fa13588aa2472d Mon Sep 17 00:00:00 2001 From: falkTX Date: Fri, 17 Feb 2017 16:13:13 +0100 Subject: [PATCH] Update DPF and Nekobi --- dpf/dgl/NanoVG.hpp | 8 +- dpf/dgl/Widget.hpp | 2 +- dpf/distrho/DistrhoPlugin.hpp | 58 +++++++++- dpf/distrho/src/DistrhoPluginInternal.hpp | 7 ++ dpf/distrho/src/DistrhoPluginJack.cpp | 125 ++++++++++++++++----- dpf/distrho/src/DistrhoPluginLV2.cpp | 21 ++-- dpf/distrho/src/DistrhoPluginLV2export.cpp | 37 +++++- dpf/distrho/src/DistrhoUILV2.cpp | 2 +- plugins/Nekobi/DistrhoPluginNekobi.cpp | 18 +++ plugins/Nekobi/DistrhoPluginNekobi.hpp | 4 +- 10 files changed, 231 insertions(+), 51 deletions(-) diff --git a/dpf/dgl/NanoVG.hpp b/dpf/dgl/NanoVG.hpp index 322c6cf..a7e68ca 100644 --- a/dpf/dgl/NanoVG.hpp +++ b/dpf/dgl/NanoVG.hpp @@ -192,10 +192,10 @@ private: @code const char* txt = "Text me up."; - textBounds(vg, x,y, txt, NULL, bounds); - beginPath(vg); - roundedRect(vg, bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); - fill(vg); + vg.textBounds(x,y, txt, NULL, bounds); + vg.beginPath(); + vg.roundedRect(bounds[0], bounds[1], bounds[2]-bounds[0], bounds[3]-bounds[1]); + vg.fill(); @endcode Note: currently only solid color fill is supported for text. diff --git a/dpf/dgl/Widget.hpp b/dpf/dgl/Widget.hpp index 1ba3c1c..74bf183 100644 --- a/dpf/dgl/Widget.hpp +++ b/dpf/dgl/Widget.hpp @@ -111,7 +111,7 @@ public: /** Mouse event. @a button The button number (1 = left, 2 = middle, 3 = right). - @a press True if the key was pressed, false if released. + @a press True if the button was pressed, false if released. @a pos The widget-relative coordinates of the pointer. @see onMouse */ diff --git a/dpf/distrho/DistrhoPlugin.hpp b/dpf/distrho/DistrhoPlugin.hpp index b31c375..fcb40dd 100644 --- a/dpf/distrho/DistrhoPlugin.hpp +++ b/dpf/distrho/DistrhoPlugin.hpp @@ -133,6 +133,29 @@ struct AudioPort { symbol() {} }; +/** + Parameter designation.@n + Allows a parameter to be specially designated for a task, like bypass. + + Each designation is unique, there must be only one parameter that uses it.@n + The use of designated parameters is completely optional. + + @note Designated parameters have strict ranges. + @see ParameterRanges::adjustForDesignation() + */ +enum ParameterDesignation { + /** + Null or unset designation. + */ + kParameterDesignationNull = 0, + + /** + Bypass designation.@n + When on (> 0.5f), it means the plugin must run in a bypassed state. + */ + kParameterDesignationBypass = 1 +}; + /** Parameter ranges.@n This is used to set the default, minimum and maximum values of a parameter. @@ -289,6 +312,11 @@ struct Parameter { */ ParameterRanges ranges; + /** + Designation for this parameter. + */ + ParameterDesignation designation; + /** MIDI CC to use by default on this parameter.@n A value of 0 or 32 (bank change) is considered invalid.@n @@ -306,6 +334,7 @@ struct Parameter { symbol(), unit(), ranges(), + designation(kParameterDesignationNull), midiCC(0) {} /** @@ -317,7 +346,32 @@ struct Parameter { symbol(s), unit(u), ranges(def, min, max), + designation(kParameterDesignationNull), midiCC(0) {} + + /** + Initialize a parameter for a specific designation. + */ + void initDesignation(ParameterDesignation d) noexcept + { + designation = d; + + switch (d) + { + case kParameterDesignationNull: + break; + case kParameterDesignationBypass: + hints = kParameterIsAutomable|kParameterIsBoolean|kParameterIsInteger; + name = "Bypass"; + symbol = "dpf_bypass"; + unit = ""; + midiCC = 0; + ranges.def = 0.0f; + ranges.min = 0.0f; + ranges.max = 1.0f; + break; + } + } }; /** @@ -541,7 +595,7 @@ public: Returns false when the host buffer is full, in which case do not call this again until the next run(). @note This function is not implemented yet!@n It's here so that developers can prepare MIDI plugins in advance.@n - If you plan to use this, please report to DPF authos so it can be implemented. + If you plan to use this, please report to DPF authors so it can be implemented. */ bool writeMidiEvent(const MidiEvent& midiEvent) noexcept; #endif @@ -661,7 +715,7 @@ protected: /** Get the value of an internal state.@n The host may call this function from any non-realtime context.@n - Must be implemented by your plugin class if DISTRHO_PLUGIN_WANT_PROGRAMS or DISTRHO_PLUGIN_WANT_FULL_STATE is enabled. + Must be implemented by your plugin class if DISTRHO_PLUGIN_WANT_FULL_STATE is enabled. @note The use of this function breaks compatibility with the DSSI format. */ virtual String getState(const char* key) const = 0; diff --git a/dpf/distrho/src/DistrhoPluginInternal.hpp b/dpf/distrho/src/DistrhoPluginInternal.hpp index 2781dfa..bbb1079 100644 --- a/dpf/distrho/src/DistrhoPluginInternal.hpp +++ b/dpf/distrho/src/DistrhoPluginInternal.hpp @@ -290,6 +290,13 @@ public: return fData->parameters[index].hints; } + ParameterDesignation getParameterDesignation(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, kParameterDesignationNull); + + return fData->parameters[index].designation; + } + bool isParameterOutput(const uint32_t index) const noexcept { return (getParameterHints(index) & kParameterIsOutput); diff --git a/dpf/distrho/src/DistrhoPluginJack.cpp b/dpf/distrho/src/DistrhoPluginJack.cpp index 8b0b0eb..b735e7d 100644 --- a/dpf/distrho/src/DistrhoPluginJack.cpp +++ b/dpf/distrho/src/DistrhoPluginJack.cpp @@ -97,28 +97,27 @@ public: #endif fClient(client) { +#if DISTRHO_PLUGIN_NUM_INPUTS > 0 || DISTRHO_PLUGIN_NUM_OUTPUTS > 0 char strBuf[0xff+1]; strBuf[0xff] = '\0'; -#if DISTRHO_PLUGIN_NUM_INPUTS > 0 +# if DISTRHO_PLUGIN_NUM_INPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) { std::snprintf(strBuf, 0xff, "in%i", i+1); fPortAudioIns[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); } -#endif - -#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 +# endif +# if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) { std::snprintf(strBuf, 0xff, "out%i", i+1); fPortAudioOuts[i] = jack_port_register(fClient, strBuf, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); } +# endif #endif -#if DISTRHO_PLUGIN_IS_SYNTH - fPortMidiIn = jack_port_register(fClient, "midi-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); -#endif + fPortEventsIn = jack_port_register(fClient, "events-in", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0); #if DISTRHO_PLUGIN_WANT_PROGRAMS if (fPlugin.getProgramCount() > 0) @@ -128,11 +127,18 @@ public: fUI.programLoaded(0); # endif } +# if DISTRHO_PLUGIN_HAS_UI + fProgramChanged = -1; +# endif #endif if (const uint32_t count = fPlugin.getParameterCount()) { fLastOutputValues = new float[count]; +#if DISTRHO_PLUGIN_HAS_UI + fParametersChanged = new bool[count]; + std::memset(fParametersChanged, 0, sizeof(bool)*count); +#endif for (uint32_t i=0; i < count; ++i) { @@ -143,15 +149,18 @@ public: else { fLastOutputValues[i] = 0.0f; -# if DISTRHO_PLUGIN_HAS_UI +#if DISTRHO_PLUGIN_HAS_UI fUI.parameterChanged(i, fPlugin.getParameterValue(i)); -# endif +#endif } } } else { fLastOutputValues = nullptr; +#if DISTRHO_PLUGIN_HAS_UI + fParametersChanged = nullptr; +#endif } jack_set_buffer_size_callback(fClient, jackBufferSizeCallback, this); @@ -192,10 +201,8 @@ public: if (fClient == nullptr) return; -#if DISTRHO_PLUGIN_IS_SYNTH - jack_port_unregister(fClient, fPortMidiIn); - fPortMidiIn = nullptr; -#endif + jack_port_unregister(fClient, fPortEventsIn); + fPortEventsIn = nullptr; #if DISTRHO_PLUGIN_NUM_INPUTS > 0 for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS; ++i) @@ -225,20 +232,31 @@ protected: if (gCloseSignalReceived) return fUI.quit(); - float value; +# if DISTRHO_PLUGIN_WANT_PROGRAMS + if (fProgramChanged >= 0) + { + fUI.programLoaded(fProgramChanged); + fProgramChanged = -1; + } +# endif for (uint32_t i=0, count=fPlugin.getParameterCount(); i < count; ++i) { - if (! fPlugin.isParameterOutput(i)) - continue; - - value = fPlugin.getParameterValue(i); + if (fPlugin.isParameterOutput(i)) + { + const float value = fPlugin.getParameterValue(i); - if (fLastOutputValues[i] == value) - continue; + if (d_isEqual(fLastOutputValues[i], value)) + continue; - fLastOutputValues[i] = value; - fUI.parameterChanged(i, value); + fLastOutputValues[i] = value; + fUI.parameterChanged(i, value); + } + else if (fParametersChanged[i]) + { + fParametersChanged[i] = false; + fUI.parameterChanged(i, fPlugin.getParameterValue(i)); + } } fUI.exec_idle(); @@ -310,14 +328,14 @@ protected: fPlugin.setTimePosition(fTimePosition); #endif -#if DISTRHO_PLUGIN_IS_SYNTH - void* const midiBuf = jack_port_get_buffer(fPortMidiIn, nframes); + void* const midiBuf = jack_port_get_buffer(fPortEventsIn, nframes); if (const uint32_t eventCount = jack_midi_get_event_count(midiBuf)) { +#if DISTRHO_PLUGIN_IS_SYNTH uint32_t midiEventCount = 0; MidiEvent midiEvents[eventCount]; - +#endif jack_midi_event_t jevent; for (uint32_t i=0; i < eventCount; ++i) @@ -325,6 +343,47 @@ protected: if (jack_midi_event_get(&jevent, midiBuf, i) != 0) break; + // Check if message is control change on channel 1 + if (jevent.buffer[0] == 0xB0 && jevent.size == 3) + { + const uint8_t control = jevent.buffer[1]; + const uint8_t value = jevent.buffer[2]; + + /* NOTE: This is not optimal, we're iterating all parameters on every CC message. + Since the JACK standalone is more of a test tool, this will do for now. */ + for (uint32_t j=0, paramCount=fPlugin.getParameterCount(); j < paramCount; ++j) + { + if (fPlugin.isParameterOutput(j)) + continue; + if (fPlugin.getParameterMidiCC(j) != control) + continue; + + const float scaled = static_cast(value)/127.0f; + const float fvalue = fPlugin.getParameterRanges(j).getUnnormalizedValue(scaled); + fPlugin.setParameterValue(j, fvalue); +#if DISTRHO_PLUGIN_HAS_UI + fParametersChanged[j] = true; +#endif + break; + } + } +#if DISTRHO_PLUGIN_WANT_PROGRAMS + // Check if message is program change on channel 1 + else if (jevent.buffer[0] == 0xC0 && jevent.size == 2) + { + const uint8_t program = jevent.buffer[1]; + + if (program < fPlugin.getProgramCount()) + { + fPlugin.loadProgram(program); +# if DISTRHO_PLUGIN_HAS_UI + fProgramChanged = program; +# endif + } + } +#endif + +#if DISTRHO_PLUGIN_IS_SYNTH MidiEvent& midiEvent(midiEvents[midiEventCount++]); midiEvent.frame = jevent.time; @@ -334,10 +393,14 @@ protected: midiEvent.dataExt = jevent.buffer; else std::memcpy(midiEvent.data, jevent.buffer, midiEvent.size); +#endif } +#if DISTRHO_PLUGIN_IS_SYNTH fPlugin.run(audioIns, audioOuts, nframes, midiEvents, midiEventCount); +#endif } +#if DISTRHO_PLUGIN_IS_SYNTH else { fPlugin.run(audioIns, audioOuts, nframes, nullptr, 0); @@ -393,9 +456,7 @@ private: #if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 jack_port_t* fPortAudioOuts[DISTRHO_PLUGIN_NUM_OUTPUTS]; #endif -#if DISTRHO_PLUGIN_IS_SYNTH - jack_port_t* fPortMidiIn; -#endif + jack_port_t* fPortEventsIn; #if DISTRHO_PLUGIN_WANT_TIMEPOS TimePosition fTimePosition; #endif @@ -403,6 +464,14 @@ private: // Temporary data float* fLastOutputValues; +#if DISTRHO_PLUGIN_HAS_UI + // Store DSP changes to send to UI + bool* fParametersChanged; +# if DISTRHO_PLUGIN_WANT_PROGRAMS + int fProgramChanged; +# endif +#endif + // ------------------------------------------------------------------- // Callbacks diff --git a/dpf/distrho/src/DistrhoPluginLV2.cpp b/dpf/distrho/src/DistrhoPluginLV2.cpp index 606c65a..387e1ac 100644 --- a/dpf/distrho/src/DistrhoPluginLV2.cpp +++ b/dpf/distrho/src/DistrhoPluginLV2.cpp @@ -512,6 +512,12 @@ public: if (fLastControlValues[i] != curValue && ! fPlugin.isParameterOutput(i)) { fLastControlValues[i] = curValue; + + if (fPlugin.getParameterDesignation(i) == kParameterDesignationBypass) + { + curValue = 1.0f - curValue; + } + fPlugin.setParameterValue(i, curValue); } } @@ -589,10 +595,14 @@ public: #if DISTRHO_PLUGIN_WANT_STATE && DISTRHO_PLUGIN_HAS_UI const uint32_t capacity = fPortEventsOut->atom.size; - bool needsInit = true; uint32_t size, offset = 0; LV2_Atom_Event* aev; + fPortEventsOut->atom.size = sizeof(LV2_Atom_Sequence_Body); + fPortEventsOut->atom.type = fURIDs.atomSequence; + fPortEventsOut->body.unit = 0; + fPortEventsOut->body.pad = 0; + // TODO - MIDI Output for (uint32_t i=0, count=fPlugin.getStateCount(); i < count; ++i) @@ -617,15 +627,6 @@ public: if (sizeof(LV2_Atom_Event) + msgSize > capacity - offset) break; - if (needsInit) - { - fPortEventsOut->atom.size = 0; - fPortEventsOut->atom.type = fURIDs.atomSequence; - fPortEventsOut->body.unit = 0; - fPortEventsOut->body.pad = 0; - needsInit = false; - } - // reserve msg space char msgBuf[msgSize]; std::memset(msgBuf, 0, msgSize); diff --git a/dpf/distrho/src/DistrhoPluginLV2export.cpp b/dpf/distrho/src/DistrhoPluginLV2export.cpp index f99e593..1ba6848 100644 --- a/dpf/distrho/src/DistrhoPluginLV2export.cpp +++ b/dpf/distrho/src/DistrhoPluginLV2export.cpp @@ -377,10 +377,34 @@ void lv2_generate_ttl(const char* const basename) pluginString += " a lv2:InputPort, lv2:ControlPort ;\n"; pluginString += " lv2:index " + String(portIndex) + " ;\n"; - pluginString += " lv2:name \"" + plugin.getParameterName(i) + "\" ;\n"; - // symbol + bool designated = false; + + // designation + if (! plugin.isParameterOutput(i)) { + switch (plugin.getParameterDesignation(i)) + { + case kParameterDesignationNull: + break; + case kParameterDesignationBypass: + designated = true; + pluginString += " lv2:name \"Enabled\" ;\n"; + pluginString += " lv2:symbol \"lv2_enabled\" ;\n"; + pluginString += " lv2:default 1 ;\n"; + pluginString += " lv2:minimum 0 ;\n"; + pluginString += " lv2:maximum 1 ;\n"; + pluginString += " lv2:portProperty lv2:toggled , lv2:integer ;\n"; + pluginString += " lv2:designation lv2:enabled ;\n"; + break; + } + } + + // name and symbol + if (! designated) + { + pluginString += " lv2:name \"" + plugin.getParameterName(i) + "\" ;\n"; + String symbol(plugin.getParameterSymbol(i)); if (symbol.isEmpty()) @@ -390,24 +414,28 @@ void lv2_generate_ttl(const char* const basename) } // ranges + if (! designated) { const ParameterRanges& ranges(plugin.getParameterRanges(i)); if (plugin.getParameterHints(i) & kParameterIsInteger) { - pluginString += " lv2:default " + String(int(plugin.getParameterValue(i))) + " ;\n"; + if (! plugin.isParameterOutput(i)) + pluginString += " lv2:default " + String(int(plugin.getParameterValue(i))) + " ;\n"; pluginString += " lv2:minimum " + String(int(ranges.min)) + " ;\n"; pluginString += " lv2:maximum " + String(int(ranges.max)) + " ;\n"; } else { - pluginString += " lv2:default " + String(plugin.getParameterValue(i)) + " ;\n"; + if (! plugin.isParameterOutput(i)) + pluginString += " lv2:default " + String(plugin.getParameterValue(i)) + " ;\n"; pluginString += " lv2:minimum " + String(ranges.min) + " ;\n"; pluginString += " lv2:maximum " + String(ranges.max) + " ;\n"; } } // unit + if (! designated) { const String& unit(plugin.getParameterUnit(i)); @@ -453,6 +481,7 @@ void lv2_generate_ttl(const char* const basename) } // hints + if (! designated) { const uint32_t hints(plugin.getParameterHints(i)); diff --git a/dpf/distrho/src/DistrhoUILV2.cpp b/dpf/distrho/src/DistrhoUILV2.cpp index 7942a1d..24713b2 100644 --- a/dpf/distrho/src/DistrhoUILV2.cpp +++ b/dpf/distrho/src/DistrhoUILV2.cpp @@ -239,7 +239,7 @@ protected: const size_t msgSize(tmpStr.length()+1); // reserve atom space - const size_t atomSize(lv2_atom_pad_size(sizeof(LV2_Atom) + msgSize)); + const size_t atomSize(sizeof(LV2_Atom) + msgSize); char atomBuf[atomSize]; std::memset(atomBuf, 0, atomSize); diff --git a/plugins/Nekobi/DistrhoPluginNekobi.cpp b/plugins/Nekobi/DistrhoPluginNekobi.cpp index 0081f0e..15839d0 100644 --- a/plugins/Nekobi/DistrhoPluginNekobi.cpp +++ b/plugins/Nekobi/DistrhoPluginNekobi.cpp @@ -135,6 +135,7 @@ DistrhoPluginNekobi::DistrhoPluginNekobi() fParams.decay = 75.0f; fParams.accent = 25.0f; fParams.volume = 75.0f; + fParams.bypass = false; // Internal stuff fSynth.waveform = 0.0f; @@ -232,6 +233,9 @@ void DistrhoPluginNekobi::initParameter(uint32_t index, Parameter& parameter) parameter.ranges.min = 0.0f; parameter.ranges.max = 100.0f; break; + case paramBypass: + parameter.initDesignation(kParameterDesignationBypass); + break; } } @@ -258,6 +262,8 @@ float DistrhoPluginNekobi::getParameterValue(uint32_t index) const return fParams.accent; case paramVolume: return fParams.volume; + case paramBypass: + return fParams.bypass ? 1.0f : 0.0f; } return 0.0f; @@ -307,6 +313,14 @@ void DistrhoPluginNekobi::setParameterValue(uint32_t index, float value) fSynth.volume = value/100.0f; DISTRHO_SAFE_ASSERT(fSynth.volume >= 0.0f && fSynth.volume <= 1.0f); break; + case paramBypass: { + const bool bypass = (value > 0.5f); + if (fParams.bypass != bypass) + { + fParams.bypass = bypass; + nekobee_synth_all_voices_off(&fSynth); + } + } break; } } @@ -342,6 +356,10 @@ void DistrhoPluginNekobi::run(const float**, float** outputs, uint32_t frames, c return; } + // ignore midi input if bypassed + if (fParams.bypass) + midiEventCount = 0; + while (framesDone < frames) { if (fSynth.nugget_remains == 0) diff --git a/plugins/Nekobi/DistrhoPluginNekobi.hpp b/plugins/Nekobi/DistrhoPluginNekobi.hpp index d8c534c..d4f5e80 100644 --- a/plugins/Nekobi/DistrhoPluginNekobi.hpp +++ b/plugins/Nekobi/DistrhoPluginNekobi.hpp @@ -42,6 +42,7 @@ public: paramDecay, paramAccent, paramVolume, + paramBypass, paramCount }; @@ -79,7 +80,7 @@ protected: uint32_t getVersion() const noexcept override { - return d_version(1, 0, 0); + return d_version(1, 1, 0); } int64_t getUniqueId() const noexcept override @@ -117,6 +118,7 @@ private: float decay; float accent; float volume; + bool bypass; } fParams; nekobee_synth_t fSynth;