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:
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.
*/