Lv2: Use port-property "logarithmic"

This also adds more min/max checks, mostly for logarithmic scales.
Since this raised some warnings for logarithmic CV ports, the CV
metadata is now also read (but CV ports are still not supported).
This commit is contained in:
Johannes Lorenz
2020-11-15 22:44:52 +01:00
committed by Johannes Lorenz
parent 7a85b4d547
commit a2e71c81de
5 changed files with 128 additions and 51 deletions

View File

@@ -108,6 +108,8 @@ struct Meta
Flow m_flow = Flow::Unknown;
Vis m_vis = Vis::None;
bool m_logarithmic = false;
bool m_optional = false;
bool m_used = true;

View File

@@ -32,18 +32,27 @@
//! LMMS Plugins should use this to indicate errors
enum PluginIssueType
{
// port flow & type
unknownPortFlow,
unknownPortType,
// channel count
tooManyInputChannels,
tooManyOutputChannels,
tooManyMidiInputChannels,
tooManyMidiOutputChannels,
noOutputChannel,
// port metadata
portHasNoDef,
portHasNoMin,
portHasNoMax,
minGreaterMax,
defaultValueNotInRange,
logScaleMinMissing,
logScaleMaxMissing,
logScaleMinMaxDifferentSigns,
// features
featureNotSupported, //!< plugin requires functionality LMMS can't offer
// misc
badPortType, //!< port type not supported
blacklisted,
noIssue

View File

@@ -50,8 +50,16 @@ const char *PluginIssue::msgFor(const PluginIssueType &it)
return "port is missing min value";
case portHasNoMax:
return "port is missing max value";
case minGreaterMax:
return "port minimum is greater than maximum";
case defaultValueNotInRange:
return "default value is not in range [min, max]";
case logScaleMinMissing:
return "logscale requires minimum value";
case logScaleMaxMissing:
return "logscale requires maximum value";
case logScaleMinMaxDifferentSigns:
return "logscale with min < 0 < max";
case featureNotSupported:
return "required feature not supported";
case badPortType:

View File

@@ -28,6 +28,7 @@
#ifdef LMMS_HAVE_LV2
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/port-props/port-props.h>
#include "Engine.h"
#include "Lv2Basics.h"
@@ -126,75 +127,106 @@ std::vector<PluginIssue> Meta::get(const LilvPlugin *plugin,
issue(unknownPortFlow, portName);
}
m_def = .0f; m_min = .0f; m_max = .0f;
m_def = .0f;
m_min = std::numeric_limits<decltype(m_min)>::lowest();
m_max = std::numeric_limits<decltype(m_max)>::max();
auto m_min_set = [this]{ return m_min != std::numeric_limits<decltype(m_min)>::lowest(); };
auto m_max_set = [this]{ return m_max != std::numeric_limits<decltype(m_max)>::max(); };
m_type = Type::Unknown;
if (isA(LV2_CORE__ControlPort))
if (isA(LV2_CORE__ControlPort) || isA(LV2_CORE__CVPort))
{
m_type = Type::Control;
// Read metadata for control ports
// CV ports are mostly the same as control ports, so we take
// mostly the same metadata
if (m_flow == Flow::Input)
if (isA(LV2_CORE__CVPort))
{
bool isToggle = m_vis == Vis::Toggled;
// currently not supported, but we can still check the metadata
issue(badPortType, "cvPort");
}
LilvNode * defN, * minN = nullptr, * maxN = nullptr;
lilv_port_get_range(plugin, lilvPort, &defN,
isToggle ? nullptr : &minN,
isToggle ? nullptr : &maxN);
AutoLilvNode def(defN), min(minN), max(maxN);
m_type = isA(LV2_CORE__CVPort) ? Type::Cv : Type::Control;
auto takeRangeValue = [&](LilvNode* node,
float& storeHere, PluginIssueType it)
{
if (node) { storeHere = lilv_node_as_float(node); }
else { issue(it, portName); }
};
bool isToggle = m_vis == Vis::Toggled;
takeRangeValue(def.get(), m_def, portHasNoDef);
if (isToggle)
{
m_min = .0f;
m_max = 1.f;
if(def.get() && m_def != m_min && m_def != m_max)
{
issue(defaultValueNotInRange, portName);
}
}
LilvNode * defN, * minN = nullptr, * maxN = nullptr;
lilv_port_get_range(plugin, lilvPort, &defN,
isToggle ? nullptr : &minN,
isToggle ? nullptr : &maxN);
AutoLilvNode def(defN), min(minN), max(maxN);
auto takeRangeValue = [&](LilvNode* node,
float& storeHere, PluginIssueType it)
{
if (node) { storeHere = lilv_node_as_float(node); }
else
{
takeRangeValue(min.get(), m_min, portHasNoMin);
takeRangeValue(max.get(), m_max, portHasNoMax);
if (hasProperty(LV2_CORE__sampleRate)) { m_sampleRate = true; }
if (def.get())
// CV ports do not require ranges
if(m_flow == Flow::Input && m_type != Type::Cv)
{
if (m_def < m_min) { issue(defaultValueNotInRange, portName); }
else if (m_def > m_max)
issue(it, portName);
}
}
};
takeRangeValue(def.get(), m_def, portHasNoDef);
if (isToggle)
{
m_min = .0f;
m_max = 1.f;
if(def.get() && m_def != m_min && m_def != m_max)
{
issue(defaultValueNotInRange, portName);
}
}
else
{
// take min/max
takeRangeValue(min.get(), m_min, portHasNoMin);
takeRangeValue(max.get(), m_max, portHasNoMax);
if(m_type == Type::Cv)
{
// no range is allowed and bashed to [-1,+1],
// but only min or only max does not make sense
if(!m_min_set() && !m_max_set())
{
m_min = -1.f;
m_max = +1.f;
}
else if(!m_min_set()) { issue(portHasNoMin, portName); }
else if(!m_max_set()) { issue(portHasNoMax, portName); }
}
if(m_min > m_max) { issue(minGreaterMax, portName); }
// sampleRate
if (hasProperty(LV2_CORE__sampleRate)) { m_sampleRate = true; }
// default value
if (def.get())
{
if (m_def < m_min) { issue(defaultValueNotInRange, portName); }
else if (m_def > m_max)
{
if(m_sampleRate)
{
if(m_sampleRate)
{
// multiplying with sample rate will hopefully lead us
// to a good default value
}
else { issue(defaultValueNotInRange, portName); }
// multiplying with sample rate will hopefully lead us
// to a good default value
}
else { issue(defaultValueNotInRange, portName); }
}
}
if (m_max - m_min > 15.0f)
{
// range too large for spinbox visualisation, use knobs
// e.g. 0...15 would be OK
m_vis = Vis::None;
}
// visualization
if (m_max - m_min > 15.0f)
{
// range too large for spinbox visualisation, use knobs
// e.g. 0...15 would be OK
m_vis = Vis::None;
}
}
}
else if (isA(LV2_CORE__AudioPort)) { m_type = Type::Audio; }
else if (isA(LV2_CORE__CVPort))
{
issue(badPortType, "cvPort");
m_type = Type::Cv;
}
else if (isA(LV2_ATOM__AtomPort))
{
AutoLilvNode uriAtomSequence(Engine::getLv2Manager()->uri(LV2_ATOM__Sequence));
@@ -221,6 +253,27 @@ std::vector<PluginIssue> Meta::get(const LilvPlugin *plugin,
}
}
if (hasProperty(LV2_PORT_PROPS__logarithmic))
{
// check min/max available
// we requre them anyways, but this will detect plugins that will
// be non-Lv2-conforming
if(m_min == std::numeric_limits<decltype(m_min)>::lowest())
{
issue(PluginIssueType::logScaleMinMissing, portName);
}
if(m_max == std::numeric_limits<decltype(m_max)>::max())
{
issue(PluginIssueType::logScaleMaxMissing, portName);
}
// forbid min < 0 < max
if(m_min < 0.f && m_max > 0.f)
{
issue(PluginIssueType::logScaleMinMaxDifferentSigns, portName);
}
m_logarithmic = true;
}
return portIssues;
}

View File

@@ -520,7 +520,12 @@ void Lv2Proc::createPort(std::size_t portNum)
nullptr, dispName));
break;
}
}
if(meta.m_logarithmic)
{
ctrl->m_connectedModel->setScaleLogarithmic();
}
} // if m_flow == Input
port = ctrl;
break;
}