DPF

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

commit 495dcc4f03d6a2ed0d30c56c885e4eb7476f4488
parent 5202889b0ea90cacf609bc29af22a835e1b42a34
Author: falkTX <falktx@falktx.com>
Date:   Sat, 12 Jun 2021 19:41:56 +0100

Initial implementation of port groups
Closes #193
Fixes #192

Diffstat:
Mdistrho/DistrhoPlugin.hpp | 144++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mdistrho/src/DistrhoPlugin.cpp | 6++++++
Mdistrho/src/DistrhoPluginInternal.hpp | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdistrho/src/DistrhoPluginLV2export.cpp | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mexamples/Parameters/ExamplePluginParameters.cpp | 38++++++++++++++++++++++++++++++++++++++
5 files changed, 351 insertions(+), 27 deletions(-)

diff --git a/distrho/DistrhoPlugin.hpp b/distrho/DistrhoPlugin.hpp @@ -138,6 +138,56 @@ static const uint32_t kParameterIsTrigger = 0x20 | kParameterIsBoolean; */ /** + 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 +}; + +/** + Predefined Port Groups Ids. + + This enumeration provides a few commonly used groups for convenient use in plugins. + For preventing conflicts with user code, negative values are used here. + When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. + + @see PortGroup + */ +enum PredefinedPortGroupsIds { + /** + Null or unset port group. + */ + kPortGroupNone = (uint32_t)-1, + + /** + A single channel audio group. + */ + kPortGroupMono = (uint32_t)-2, + + /** + A 2-channel discrete stereo audio group, + where the 1st audio port is the left channel and the 2nd port is the right channel. + */ + kPortGroupStereo = (uint32_t)-3 +}; + +/** Audio Port. Can be used as CV port by specifying kAudioPortIsCV in hints,@n @@ -166,35 +216,23 @@ struct AudioPort { String symbol; /** - Default constructor for a regular audio port. - */ - AudioPort() noexcept - : hints(0x0), - name(), - 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. + The group id that this audio/cv port belongs to. + No group is assigned by default. - @note Designated parameters have strict ranges. - @see ParameterRanges::adjustForDesignation() - */ -enum ParameterDesignation { - /** - Null or unset designation. + You can use a group from PredefinedPortGroups or roll your own.@n + When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. + @see PortGroup, Plugin::initPortGroup */ - kParameterDesignationNull = 0, + uint32_t groupId; /** - Bypass designation.@n - When on (> 0.5f), it means the plugin must run in a bypassed state. + Default constructor for a regular audio port. */ - kParameterDesignationBypass = 1 + AudioPort() noexcept + : hints(0x0), + name(), + symbol(), + groupId(kPortGroupNone) {} }; /** @@ -472,6 +510,16 @@ struct Parameter { uint8_t midiCC; /** + The group id that this parameter belongs to. + No group is assigned by default. + + You can use a group from PredefinedPortGroups or roll your own.@n + When rolling your own port groups, you MUST start their group ids from 0 and they MUST be sequential. + @see PortGroup, Plugin::initPortGroup + */ + uint32_t groupId; + + /** Default constructor for a null parameter. */ Parameter() noexcept @@ -483,7 +531,8 @@ struct Parameter { ranges(), enumValues(), designation(kParameterDesignationNull), - midiCC(0) {} + midiCC(0), + groupId(kPortGroupNone) {} /** Constructor using custom values. @@ -497,7 +546,8 @@ struct Parameter { ranges(def, min, max), enumValues(), designation(kParameterDesignationNull), - midiCC(0) {} + midiCC(0), + groupId(kPortGroupNone) {} /** Initialize a parameter for a specific designation. @@ -517,6 +567,7 @@ struct Parameter { symbol = "dpf_bypass"; unit = ""; midiCC = 0; + groupId = kPortGroupNone; ranges.def = 0.0f; ranges.min = 0.0f; ranges.max = 1.0f; @@ -526,6 +577,40 @@ struct Parameter { }; /** + Port Group.@n + Allows to group together audio/cv ports or parameters. + + Each unique group MUST have an unique symbol and a name. + A group can be applied to both inputs and outputs (at the same time). + The same group cannot be used in audio ports and parameters. + + An audio port group logically combines ports which should be considered part of the same stream.@n + For example, two audio ports in a group may form a stereo stream. + + A parameter group provides meta-data to the host to indicate that some parameters belong together. + + The use of port groups is completely optional. + + @see Plugin::initPortGroup, AudioPort::group, Parameter::group + */ +struct PortGroup { + /** + The name of this port group.@n + A port group name can contain any character, but hosts might have a hard time with non-ascii ones.@n + The name doesn't have to be unique within a plugin instance, but it's recommended. + */ + String name; + + /** + The symbol of this port group.@n + A port group symbol is a short restricted name used as a machine and human readable identifier.@n + The first character must be one of _, a-z or A-Z and subsequent characters can be from _, a-z, A-Z and 0-9. + @note Port group symbols MUST be unique within a plugin instance. + */ + String symbol; +}; + +/** MIDI event. */ struct MidiEvent { @@ -860,6 +945,13 @@ protected: */ virtual void initParameter(uint32_t index, Parameter& parameter) = 0; + /** + Initialize the port group @a groupId.@n + This function will be called once, + shortly after the plugin is created and all audio ports and parameters have been enumerated. + */ + virtual void initPortGroup(uint32_t groupId, PortGroup& portGroup); + #if DISTRHO_PLUGIN_WANT_PROGRAMS /** Set the name of the program @a index.@n diff --git a/distrho/src/DistrhoPlugin.cpp b/distrho/src/DistrhoPlugin.cpp @@ -32,6 +32,7 @@ const String PluginExporter::sFallbackString; const AudioPort PluginExporter::sFallbackAudioPort; const ParameterRanges PluginExporter::sFallbackRanges; const ParameterEnumerationValues PluginExporter::sFallbackEnumValues; +const PortGroupWithId PluginExporter::sFallbackPortGroup; /* ------------------------------------------------------------------------------------------------------------ * Plugin */ @@ -143,6 +144,11 @@ void Plugin::initAudioPort(bool input, uint32_t index, AudioPort& port) } } +void Plugin::initPortGroup(const uint32_t groupId, PortGroup& portGroup) +{ + fillInPredefinedPortGroupData(groupId, portGroup); +} + /* ------------------------------------------------------------------------------------------------------------ * Callbacks (optional) */ diff --git a/distrho/src/DistrhoPluginInternal.hpp b/distrho/src/DistrhoPluginInternal.hpp @@ -18,6 +18,9 @@ #define DISTRHO_PLUGIN_INTERNAL_HPP_INCLUDED #include "../DistrhoPlugin.hpp" +#include "DistrhoDefines.h" + +#include <set> START_NAMESPACE_DISTRHO @@ -40,6 +43,35 @@ typedef bool (*writeMidiFunc) (void* ptr, const MidiEvent& midiEvent); typedef bool (*requestParameterValueChangeFunc) (void* ptr, uint32_t index, float value); // ----------------------------------------------------------------------- +// Helpers + +struct PortGroupWithId : PortGroup { + uint32_t groupId; + + PortGroupWithId() + : PortGroup(), + groupId(kPortGroupNone) {} +}; + +static void fillInPredefinedPortGroupData(const uint32_t groupId, PortGroup& portGroup) +{ + switch (groupId) + { + case kPortGroupNone: + portGroup.name.clear(); + portGroup.symbol.clear(); + break; + case kPortGroupMono: + portGroup.name = "Mono"; + portGroup.symbol = "dpf_mono"; + break; + case kPortGroupStereo: + portGroup.name = "Stereo"; + portGroup.symbol = "dpf_stereo"; + break; + } +} +// ----------------------------------------------------------------------- // Plugin private data struct Plugin::PrivateData { @@ -53,6 +85,9 @@ struct Plugin::PrivateData { uint32_t parameterOffset; Parameter* parameters; + uint32_t portGroupCount; + PortGroupWithId* portGroups; + #if DISTRHO_PLUGIN_WANT_PROGRAMS uint32_t programCount; String* programNames; @@ -89,6 +124,8 @@ struct Plugin::PrivateData { parameterCount(0), parameterOffset(0), parameters(nullptr), + portGroupCount(0), + portGroups(nullptr), #if DISTRHO_PLUGIN_WANT_PROGRAMS programCount(0), programNames(nullptr), @@ -144,6 +181,12 @@ struct Plugin::PrivateData { parameters = nullptr; } + if (portGroups != nullptr) + { + delete[] portGroups; + portGroups = nullptr; + } + #if DISTRHO_PLUGIN_WANT_PROGRAMS if (programNames != nullptr) { @@ -216,11 +259,42 @@ public: fPlugin->initAudioPort(false, i, fData->audioPorts[j]); # endif } -#endif +#endif // DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) fPlugin->initParameter(i, fData->parameters[i]); + { + std::set<uint32_t> portGroupIndices; + +#if DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS+DISTRHO_PLUGIN_NUM_OUTPUTS; ++i) + portGroupIndices.insert(fData->audioPorts[i].groupId); +#endif + for (uint32_t i=0, count=fData->parameterCount; i < count; ++i) + portGroupIndices.insert(fData->parameters[i].groupId); + + portGroupIndices.erase(kPortGroupNone); + + if (const size_t portGroupSize = portGroupIndices.size()) + { + fData->portGroups = new PortGroupWithId[portGroupSize]; + fData->portGroupCount = portGroupSize; + + uint32_t index = 0; + for (std::set<uint32_t>::iterator it = portGroupIndices.begin(); it != portGroupIndices.end(); ++it, ++index) + { + PortGroupWithId& portGroup(fData->portGroups[index]); + portGroup.groupId = *it; + + if (portGroup.groupId < portGroupSize) + fPlugin->initPortGroup(portGroup.groupId, portGroup); + else + fillInPredefinedPortGroupData(portGroup.groupId, portGroup); + } + } + } + #if DISTRHO_PLUGIN_WANT_PROGRAMS for (uint32_t i=0, count=fData->programCount; i < count; ++i) fPlugin->initProgramName(i, fData->programNames[i]); @@ -443,6 +517,13 @@ public: return fData->parameters[index].midiCC; } + uint32_t getParameterGroupId(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->parameterCount, kPortGroupNone); + + return fData->parameters[index].groupId; + } + float getParameterValue(const uint32_t index) const { DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr, 0.0f); @@ -459,6 +540,42 @@ public: fPlugin->setParameterValue(index, value); } + uint32_t getPortGroupCount() const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); + + return fData->portGroupCount; + } + + const PortGroupWithId& getPortGroupById(const uint32_t groupId) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && fData->portGroupCount != 0, sFallbackPortGroup); + + for (uint32_t i=0; i < fData->portGroupCount; ++i) + { + const PortGroupWithId& portGroup(fData->portGroups[i]); + + if (portGroup.groupId == groupId) + return portGroup; + } + + return sFallbackPortGroup; + } + + const PortGroupWithId& getPortGroupByIndex(const uint32_t index) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr && index < fData->portGroupCount, sFallbackPortGroup); + + return fData->portGroups[index]; + } + + const String& getPortGroupSymbolForId(const uint32_t groupId) const noexcept + { + DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, sFallbackString); + + return getPortGroupById(groupId).symbol; + } + #if DISTRHO_PLUGIN_WANT_PROGRAMS uint32_t getProgramCount() const noexcept { @@ -695,6 +812,7 @@ private: static const AudioPort sFallbackAudioPort; static const ParameterRanges sFallbackRanges; static const ParameterEnumerationValues sFallbackEnumValues; + static const PortGroupWithId sFallbackPortGroup; DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginExporter) DISTRHO_PREVENT_HEAP_ALLOCATION diff --git a/distrho/src/DistrhoPluginLV2export.cpp b/distrho/src/DistrhoPluginLV2export.cpp @@ -24,6 +24,7 @@ #include "lv2/options.h" #include "lv2/parameters.h" #include "lv2/patch.h" +#include "lv2/port-groups.h" #include "lv2/port-props.h" #include "lv2/presets.h" #include "lv2/resize-port.h" @@ -333,6 +334,7 @@ void lv2_generate_ttl(const char* const basename) pluginString += "@prefix lv2: <" LV2_CORE_PREFIX "> .\n"; pluginString += "@prefix mod: <http://moddevices.com/ns/mod#> .\n"; pluginString += "@prefix opts: <" LV2_OPTIONS_PREFIX "> .\n"; + pluginString += "@prefix pg: <" LV2_PORT_GROUPS_PREFIX "> .\n"; pluginString += "@prefix patch: <" LV2_PATCH_PREFIX "> .\n"; pluginString += "@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .\n"; pluginString += "@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n"; @@ -428,6 +430,10 @@ void lv2_generate_ttl(const char* const basename) if (port.hints & kAudioPortIsSidechain) pluginString += " lv2:portProperty lv2:isSideChain;\n"; + if (port.groupId != kPortGroupNone) + pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" + + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n"; + // set ranges if (port.hints & kCVPortHasBipolarRange) { @@ -502,6 +508,10 @@ void lv2_generate_ttl(const char* const basename) if (port.hints & kAudioPortIsSidechain) pluginString += " lv2:portProperty lv2:isSideChain;\n"; + if (port.groupId != kPortGroupNone) + pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" + + plugin.getPortGroupSymbolForId(port.groupId) + "> ;\n"; + // set ranges if (port.hints & kCVPortHasBipolarRange) { @@ -789,6 +799,15 @@ void lv2_generate_ttl(const char* const basename) pluginString += " lv2:portProperty <" LV2_PORT_PROPS__expensive "> ,\n"; pluginString += " <" LV2_KXSTUDIO_PROPERTIES__NonAutomable "> ;\n"; } + + // TODO midiCC + + // group + const uint32_t groupId = plugin.getParameterGroupId(i); + + if (groupId != kPortGroupNone) + pluginString += " pg:group <" DISTRHO_PLUGIN_URI "#portGroup_" + + plugin.getPortGroupSymbolForId(groupId) + "> ;\n"; } // ! designated if (i+1 == count) @@ -873,6 +892,57 @@ void lv2_generate_ttl(const char* const basename) pluginString += " lv2:minorVersion " + String(minorVersion) + " .\n"; } + // port groups + if (const uint32_t portGroupCount = plugin.getPortGroupCount()) + { + bool isInput, isOutput; + + for (uint32_t i = 0; i < portGroupCount; ++i) + { + const PortGroupWithId& portGroup(plugin.getPortGroupByIndex(i)); + DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.groupId != kPortGroupNone); + DISTRHO_SAFE_ASSERT_CONTINUE(portGroup.symbol.isNotEmpty()); + + pluginString += "\n<" DISTRHO_PLUGIN_URI "#portGroup_" + portGroup.symbol + ">\n"; + isInput = isOutput = false; + +#if DISTRHO_PLUGIN_NUM_INPUTS > 0 + for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_INPUTS && !isInput; ++i) + isInput = plugin.getAudioPort(true, i).groupId == portGroup.groupId; +#endif + +#if DISTRHO_PLUGIN_NUM_OUTPUTS > 0 + for (uint32_t i=0; i < DISTRHO_PLUGIN_NUM_OUTPUTS && !isOutput; ++i) + isOutput = plugin.getAudioPort(false, i).groupId == portGroup.groupId; +#endif + + for (uint32_t i=0, count=plugin.getParameterCount(); i < count && (!isInput || !isOutput); ++i) + { + if (plugin.getParameterGroupId(i) == portGroup.groupId) + { + isInput = isInput || plugin.isParameterInput(i); + isOutput = isOutput || plugin.isParameterOutput(i); + } + } + + pluginString += " a "; + if (isInput && !isOutput) + pluginString += "pg:InputGroup"; + else if (isOutput && !isInput) + pluginString += "pg:OutputGroup"; + else + pluginString += "pg:Group"; + pluginString += " ;\n"; + +#if 0 + pluginString += " rdfs:label \"" + portGroup.name + "\" ;\n"; +#else + pluginString += " lv2:name \"" + portGroup.name + "\" ;\n"; +#endif + pluginString += " lv2:symbol \"" + portGroup.symbol + "\" .\n"; + } + } + pluginFile << pluginString << std::endl; pluginFile.close(); std::cout << " done!" << std::endl; diff --git a/examples/Parameters/ExamplePluginParameters.cpp b/examples/Parameters/ExamplePluginParameters.cpp @@ -104,6 +104,12 @@ The plugin will be treated as an effect, but it will not change the host audio." /* -------------------------------------------------------------------------------------------------------- * Init */ + enum { + kPortGroupTop = 0, + kPortGroupMiddle, + kPortGroupBottom + }; + /** Initialize the parameter @a index. This function will be called once, shortly after the plugin is created. @@ -137,30 +143,39 @@ The plugin will be treated as an effect, but it will not change the host audio." { case 0: parameter.name = "top-left"; + parameter.groupId = kPortGroupTop; break; case 1: parameter.name = "top-center"; + parameter.groupId = kPortGroupTop; break; case 2: parameter.name = "top-right"; + parameter.groupId = kPortGroupTop; break; case 3: parameter.name = "middle-left"; + parameter.groupId = kPortGroupMiddle; break; case 4: parameter.name = "middle-center"; + parameter.groupId = kPortGroupMiddle; break; case 5: parameter.name = "middle-right"; + parameter.groupId = kPortGroupMiddle; break; case 6: parameter.name = "bottom-left"; + parameter.groupId = kPortGroupBottom; break; case 7: parameter.name = "bottom-center"; + parameter.groupId = kPortGroupBottom; break; case 8: parameter.name = "bottom-right"; + parameter.groupId = kPortGroupBottom; break; } @@ -172,6 +187,29 @@ The plugin will be treated as an effect, but it will not change the host audio." } /** + Initialize the port group @a groupId.@n + This function will be called once, + shortly after the plugin is created and all audio ports and parameters have been enumerated. + */ + void initPortGroup(uint32_t groupId, PortGroup& portGroup) override + { + switch (groupId) { + case kPortGroupTop: + portGroup.name = "Top"; + portGroup.symbol = "top"; + break; + case kPortGroupMiddle: + portGroup.name = "Middle"; + portGroup.symbol = "middle"; + break; + case kPortGroupBottom: + portGroup.name = "Bottom"; + portGroup.symbol = "bottom"; + break; + } + } + + /** Set the name of the program @a index. This function will be called once, shortly after the plugin is created. */