Display gain and midiNote plugin parameters/properties nicely.

Show fancy values on generic GUI controls, automation lane controls, and automation lane verbose cursor.
Fix text display of midiNote values.
Make bigstep of midiNote parameters 12 (one octave).
Add ARDOUR::value_as_string() as a stateless one-stop-shop for value printing.
This commit is contained in:
David Robillard 2014-11-02 01:29:33 -05:00
parent 8a128b33d3
commit 47c4929bc2
10 changed files with 175 additions and 97 deletions

View File

@ -58,6 +58,7 @@
#include "ardour/event_type_map.h"
#include "ardour/session.h"
#include "ardour/value_as_string.h"
#include "i18n.h"
@ -73,6 +74,7 @@ AutomationLine::AutomationLine (const string& name,
TimeAxisView& tv,
ArdourCanvas::Item& parent,
boost::shared_ptr<AutomationList> al,
const ParameterDescriptor& desc,
Evoral::TimeConverter<double, framepos_t>* converter)
: trackview (tv)
, _name (name)
@ -81,6 +83,7 @@ AutomationLine::AutomationLine (const string& name,
, _parent_group (parent)
, _offset (0)
, _maximum_time (max_framepos)
, _desc (desc)
{
if (converter) {
_our_time_converter = false;
@ -112,7 +115,8 @@ AutomationLine::AutomationLine (const string& name,
trackview.session()->register_with_memento_command_factory(alist->id(), this);
if (alist->parameter().type() == GainAutomation ||
alist->parameter().type() == EnvelopeAutomation) {
alist->parameter().type() == EnvelopeAutomation ||
desc.unit == ParameterDescriptor::DB) {
set_uses_gain_mapping (true);
}
@ -356,24 +360,20 @@ AutomationLine::get_verbose_cursor_relative_string (double original, double frac
string
AutomationLine::fraction_to_string (double fraction) const
{
char buf[32];
if (_uses_gain_mapping) {
char buf[32];
if (fraction == 0.0) {
snprintf (buf, sizeof (buf), "-inf");
} else {
snprintf (buf, sizeof (buf), "%.1f", accurate_coefficient_to_dB (slider_position_to_gain_with_max (fraction, Config->get_max_gain())));
}
return buf;
} else {
view_to_model_coord_y (fraction);
if (EventTypeMap::instance().is_integer (alist->parameter())) {
snprintf (buf, sizeof (buf), "%d", (int)fraction);
} else {
snprintf (buf, sizeof (buf), "%.2f", fraction);
}
return ARDOUR::value_as_string (_desc, fraction);
}
return buf;
return ""; /*NOTREACHED*/
}
/**
@ -406,11 +406,7 @@ AutomationLine::fraction_to_relative_string (double original, double fraction) c
} else {
view_to_model_coord_y (original);
view_to_model_coord_y (fraction);
if (EventTypeMap::instance().is_integer (alist->parameter())) {
snprintf (buf, sizeof (buf), "%d", (int)fraction - (int)original);
} else {
snprintf (buf, sizeof (buf), "%.2f", fraction - original);
}
return ARDOUR::value_as_string (_desc, fraction - original);
}
return buf;

View File

@ -34,6 +34,7 @@
#include "pbd/memento_command.h"
#include "ardour/automation_list.h"
#include "ardour/parameter_descriptor.h"
#include "ardour/types.h"
#include "canvas/types.h"
@ -64,6 +65,7 @@ public:
TimeAxisView& tv,
ArdourCanvas::Item& parent,
boost::shared_ptr<ARDOUR::AutomationList> al,
const ARDOUR::ParameterDescriptor& desc,
Evoral::TimeConverter<double, ARDOUR::framepos_t>* converter = 0);
virtual ~AutomationLine ();
@ -234,6 +236,8 @@ private:
/** maximum time that a point on this line can be at, relative to the position of its region or start of its track */
ARDOUR::framecnt_t _maximum_time;
const ARDOUR::ParameterDescriptor _desc;
friend class AudioRegionGainLine;
};

View File

@ -250,7 +250,8 @@ AutomationTimeAxisView::AutomationTimeAxisView (
ARDOUR::EventTypeMap::instance().to_symbol(_parameter),
*this,
*_canvas_display,
_control->alist()
_control->alist(),
_control->desc()
)
);

View File

@ -41,6 +41,7 @@
#include "ardour/plugin.h"
#include "ardour/plugin_insert.h"
#include "ardour/session.h"
#include "ardour/value_as_string.h"
#include "ardour_ui.h"
#include "prompter.h"
@ -504,51 +505,23 @@ GenericPluginUI::automation_state_changed (ControlUI* cui)
}
}
bool
GenericPluginUI::integer_printer (char buf[32], Adjustment &adj, ControlUI* cui)
{
float const v = adj.get_value ();
if (cui->scale_points) {
ScalePoints::const_iterator i = cui->scale_points->begin ();
while (i != cui->scale_points->end() && i->second != v) {
++i;
}
if (i != cui->scale_points->end ()) {
snprintf (buf, 32, "%s", i->first.c_str());
return true;
}
}
snprintf (buf, 32, "%.0f", v);
float const v = cui->control->interface_to_internal(adj.get_value ());
const std::string& str = ARDOUR::value_as_string(cui->control->desc(), Variant(v));
const size_t len = str.copy(buf, 31);
buf[len] = '\0';
return true;
}
bool
GenericPluginUI::midinote_printer (char buf[32], Adjustment &adj, ControlUI* cui)
{
float const v = adj.get_value ();
if (cui->scale_points) {
ScalePoints::const_iterator i = cui->scale_points->begin ();
while (i != cui->scale_points->end() && i->second != v) {
++i;
}
if (i != cui->scale_points->end ()) {
snprintf (buf, 32, "%s", i->first.c_str());
return true;
}
}
if (v >= 0 && v <= 127) {
int mn = rint(v);
const char notename[12][3] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
snprintf (buf, 32, "%s %d", notename[mn%12], (mn/12)-2);
} else {
snprintf (buf, 32, "%.0f", v);
}
float const v = cui->control->interface_to_internal(adj.get_value ());
const std::string& str = ARDOUR::value_as_string(cui->control->desc(), Variant(v));
const size_t len = str.copy(buf, 31);
buf[len] = '\0';
return true;
}
@ -687,9 +660,9 @@ GenericPluginUI::build_control_ui (const ParameterDescriptor& desc,
Adjustment* adj = control_ui->controller->adjustment();
if (desc.integer_step) {
control_ui->clickbox = new ClickBox (adj, "PluginUIClickBox");
control_ui->clickbox = new ClickBox (adj, "PluginUIClickBox", desc.enumeration);
Gtkmm2ext::set_size_request_to_display_given_text (*control_ui->clickbox, "g9999999", 2, 2);
if (desc.midinote) {
if (desc.unit == ParameterDescriptor::MIDI_NOTE) {
control_ui->clickbox->set_printer (sigc::bind (sigc::mem_fun (*this, &GenericPluginUI::midinote_printer), control_ui));
} else {
control_ui->clickbox->set_printer (sigc::bind (sigc::mem_fun (*this, &GenericPluginUI::integer_printer), control_ui));

View File

@ -34,7 +34,7 @@ MidiAutomationLine::MidiAutomationLine (
boost::shared_ptr<ARDOUR::MidiRegion> region,
Evoral::Parameter parameter,
Evoral::TimeConverter<double, ARDOUR::framepos_t>* converter)
: AutomationLine (name, tav, parent, list, converter)
: AutomationLine (name, tav, parent, list, parameter, converter)
, _region (region)
, _parameter (parameter)
{

View File

@ -38,7 +38,7 @@ using namespace ARDOUR;
using namespace PBD;
AudioRegionGainLine::AudioRegionGainLine (const string & name, AudioRegionView& r, ArdourCanvas::Container& parent, boost::shared_ptr<AutomationList> l)
: AutomationLine (name, r.get_time_axis_view(), parent, l)
: AutomationLine (name, r.get_time_axis_view(), parent, l, l->parameter())
, rv (r)
{
// If this isn't true something is horribly wrong, and we'll get catastrophic gain values

View File

@ -33,24 +33,34 @@ typedef std::map<const std::string, const float> ScalePoints;
*/
struct ParameterDescriptor
{
enum Unit {
NONE, ///< No unit
DB, ///< Decibels
MIDI_NOTE, ///< MIDI note number
};
ParameterDescriptor(const Evoral::Parameter& parameter)
: key((uint32_t)-1)
, datatype(Variant::VOID)
, normal(parameter.normal())
, lower(parameter.min())
, upper(parameter.max())
, step(0)
, smallstep((upper - lower) / 100.0)
, largestep((upper - lower) / 10.0)
, integer_step(false)
, step((upper - lower) / 100.0f)
, smallstep((upper - lower) / 1000.0f)
, largestep((upper - lower) / 10.0f)
, integer_step(parameter.type() >= MidiCCAutomation &&
parameter.type() <= MidiChannelPressureAutomation)
, toggled(parameter.toggled())
, logarithmic(false)
, sr_dependent(false)
, min_unbound(0)
, max_unbound(0)
, enumeration(false)
, midinote(false)
{}
{
if (parameter.type() == GainAutomation) {
unit = DB;
}
}
ParameterDescriptor()
: key((uint32_t)-1)
@ -68,13 +78,33 @@ struct ParameterDescriptor
, min_unbound(0)
, max_unbound(0)
, enumeration(false)
, midinote(false)
{}
/// Set step, smallstep, and largestep, based on current description
void update_steps() {
if (unit == ParameterDescriptor::MIDI_NOTE) {
step = smallstep = 1; // semitone
largestep = 12; // octave
} else {
const float delta = upper - lower;
step = delta / 1000.0f;
smallstep = delta / 10000.0f;
largestep = delta / 10.0f;
if (integer_step) {
step = rint(step);
largestep = rint(largestep);
// leave smallstep alone for fine tuning
}
}
}
std::string label;
boost::shared_ptr<ScalePoints> scale_points;
uint32_t key; ///< for properties
Variant::Type datatype; ///< for properties
Unit unit;
float normal;
float lower; ///< for frequencies, this is in Hz (not a fraction of the sample rate)
float upper; ///< for frequencies, this is in Hz (not a fraction of the sample rate)
@ -88,7 +118,6 @@ struct ParameterDescriptor
bool min_unbound;
bool max_unbound;
bool enumeration;
bool midinote; ///< only used if integer_step is also true
};
} // namespace ARDOUR

View File

@ -0,0 +1,81 @@
/*
Copyright (C) 2014 Paul Davis
Author: David Robillard
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __ardour_value_as_string_h__
#define __ardour_value_as_string_h__
#include <stddef.h>
#include "ardour/parameter_descriptor.h"
namespace ARDOUR {
inline std::string
value_as_string(const ARDOUR::ParameterDescriptor& desc,
double v)
{
char buf[32];
if (desc.scale_points) {
// Check if value is on a scale point
for (ARDOUR::ScalePoints::const_iterator i = desc.scale_points->begin();
i != desc.scale_points->end();
++i) {
if (i->second == v) {
return i->first; // Found it, return scale point label
}
}
}
// Value is not a scale point, print it normally
if (desc.unit == ARDOUR::ParameterDescriptor::MIDI_NOTE) {
if (v >= 0 && v <= 127) {
const int num = rint(v);
static const char names[12][3] = {
"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
};
snprintf(buf, sizeof(buf), "%s %d", names[num % 12], (num / 12) - 2);
} else {
// Odd, invalid range, just print the number
snprintf(buf, sizeof(buf), "%.0f", v);
}
} else if (desc.integer_step) {
snprintf(buf, sizeof(buf), "%d", (int)v);
} else {
snprintf(buf, sizeof(buf), "%.2f", v);
}
if (desc.unit == ARDOUR::ParameterDescriptor::DB) {
// TODO: Move proper dB printing from AutomationLine here
return std::string(buf) + " dB";
}
return buf;
}
inline std::string
value_as_string(const ARDOUR::ParameterDescriptor& desc,
const ARDOUR::Variant& val)
{
// Only numeric support, for now
return value_as_string(desc, val.to_double());
}
} // namespace ARDOUR
#endif /* __ardour_value_as_string_h__ */

View File

@ -35,6 +35,7 @@
#include "ardour/plugin_insert.h"
#include "ardour/session.h"
#include "ardour/uri_map.h"
#include "ardour/value_as_string.h"
#include "i18n.h"
@ -474,19 +475,5 @@ Automatable::clear_controls ()
string
Automatable::value_as_string (boost::shared_ptr<AutomationControl> ac) const
{
std::stringstream s;
/* this is a the default fallback for this virtual method. Derived Automatables
are free to override this to display the values of their parameters/controls
in different ways.
*/
// Hack to display CC as integer value, rather than double
if (ac->parameter().type() == MidiCCAutomation) {
s << lrint (ac->get_value());
} else {
s << std::fixed << std::setprecision(3) << ac->get_value();
}
return s.str ();
return ARDOUR::value_as_string(ac->desc(), ac->get_value());
}

View File

@ -66,6 +66,7 @@
#include "lv2/lv2plug.in/ns/ext/worker/worker.h"
#include "lv2/lv2plug.in/ns/ext/resize-port/resize-port.h"
#include "lv2/lv2plug.in/ns/extensions/ui/ui.h"
#include "lv2/lv2plug.in/ns/extensions/units/units.h"
#include "lv2/lv2plug.in/ns/ext/patch/patch.h"
#ifdef HAVE_LV2_1_2_0
#include "lv2/lv2plug.in/ns/ext/buf-size/buf-size.h"
@ -138,6 +139,7 @@ public:
LilvNode* ui_GtkUI;
LilvNode* ui_external;
LilvNode* ui_externalkx;
LilvNode* units_db;
LilvNode* units_unit;
LilvNode* units_midiNote;
LilvNode* patch_writable;
@ -1312,10 +1314,10 @@ LV2Plugin::get_property_descriptor(uint32_t id) const
}
static void
set_parameter_descriptor(LV2World& world,
ParameterDescriptor& desc,
Variant::Type datatype,
const LilvNode* subject)
load_parameter_descriptor(LV2World& world,
ParameterDescriptor& desc,
Variant::Type datatype,
const LilvNode* subject)
{
LilvWorld* lworld = _world.world;
LilvNode* label = lilv_world_get(lworld, subject, _world.rdfs_label, NULL);
@ -1337,6 +1339,12 @@ set_parameter_descriptor(LV2World& world,
desc.datatype = datatype;
desc.toggled |= datatype == Variant::BOOL;
desc.integer_step |= datatype == Variant::INT || datatype == Variant::LONG;
if (lilv_world_ask(lworld, subject, _world.units_unit, _world.units_midiNote)) {
desc.unit = ParameterDescriptor::MIDI_NOTE;
} else if (lilv_world_ask(lworld, subject, _world.units_unit, _world.units_db)) {
desc.unit = ParameterDescriptor::DB;
}
desc.update_steps();
}
void
@ -1368,7 +1376,7 @@ LV2Plugin::load_supported_properties(PropertyDescriptors& descs)
ParameterDescriptor desc;
desc.key = _uri_map.uri_to_id(lilv_node_as_uri(prop));
desc.datatype = datatype;
set_parameter_descriptor(_world, desc, datatype, prop);
load_parameter_descriptor(_world, desc, datatype, prop);
descs.insert(std::make_pair(desc.key, desc));
lilv_node_free(range);
@ -1560,6 +1568,8 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c
lilv_port_get_range(_impl->plugin, port, &def, &min, &max);
portunits = lilv_port_get_value(_impl->plugin, port, _world.units_unit);
// TODO: Once we can rely on lilv 0.18.0 being present,
// load_parameter_descriptor() can be used for ports as well
desc.integer_step = lilv_port_has_property(_impl->plugin, port, _world.lv2_integer);
desc.toggled = lilv_port_has_property(_impl->plugin, port, _world.lv2_toggled);
desc.logarithmic = lilv_port_has_property(_impl->plugin, port, _world.ext_logarithmic);
@ -1567,7 +1577,11 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c
desc.label = lilv_node_as_string(lilv_port_get_name(_impl->plugin, port));
desc.lower = min ? lilv_node_as_float(min) : 0.0f;
desc.upper = max ? lilv_node_as_float(max) : 1.0f;
desc.midinote = lilv_nodes_contains(portunits, _world.units_midiNote);
if (lilv_nodes_contains(portunits, _world.units_midiNote)) {
desc.unit = ParameterDescriptor::MIDI_NOTE;
} else if (lilv_nodes_contains(portunits, _world.units_db)) {
desc.unit = ParameterDescriptor::DB;
}
if (desc.sr_dependent) {
desc.lower *= _session.frame_rate ();
@ -1577,20 +1591,11 @@ LV2Plugin::get_parameter_descriptor(uint32_t which, ParameterDescriptor& desc) c
desc.min_unbound = false; // TODO: LV2 extension required
desc.max_unbound = false; // TODO: LV2 extension required
if (desc.integer_step) {
desc.step = 1.0;
desc.smallstep = 0.1;
desc.largestep = 10.0;
} else {
const float delta = desc.upper - desc.lower;
desc.step = delta / 1000.0f;
desc.smallstep = delta / 10000.0f;
desc.largestep = delta / 10.0f;
}
desc.enumeration = lilv_port_has_property(_impl->plugin, port, _world.lv2_enumeration);
desc.scale_points = get_scale_points(which);
desc.update_steps();
lilv_node_free(def);
lilv_node_free(min);
lilv_node_free(max);
@ -2274,8 +2279,9 @@ LV2World::LV2World()
ui_GtkUI = lilv_new_uri(world, LV2_UI__GtkUI);
ui_external = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/ui#external");
ui_externalkx = lilv_new_uri(world, "http://kxstudio.sf.net/ns/lv2ext/external-ui#Widget");
units_unit = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#unit");
units_midiNote = lilv_new_uri(world, "http://lv2plug.in/ns/extensions/units#midiNote");
units_unit = lilv_new_uri(world, LV2_UNITS__unit);
units_midiNote = lilv_new_uri(world, LV2_UNITS__midiNote);
units_db = lilv_new_uri(world, LV2_UNITS__db);
patch_writable = lilv_new_uri(world, LV2_PATCH__writable);
patch_Message = lilv_new_uri(world, LV2_PATCH__Message);
}
@ -2285,6 +2291,7 @@ LV2World::~LV2World()
lilv_node_free(patch_Message);
lilv_node_free(patch_writable);
lilv_node_free(units_midiNote);
lilv_node_free(units_db);
lilv_node_free(units_unit);
lilv_node_free(ui_externalkx);
lilv_node_free(ui_external);