From 47c4929bc285da6d752e68aa5a32cf73f20b9f22 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 2 Nov 2014 01:29:33 -0500 Subject: 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. --- gtk2_ardour/automation_line.cc | 24 ++++----- gtk2_ardour/automation_line.h | 4 ++ gtk2_ardour/automation_time_axis.cc | 3 +- gtk2_ardour/generic_pluginui.cc | 49 +++++-------------- gtk2_ardour/midi_automation_line.cc | 2 +- gtk2_ardour/region_gain_line.cc | 2 +- libs/ardour/ardour/parameter_descriptor.h | 45 ++++++++++++++--- libs/ardour/ardour/value_as_string.h | 81 +++++++++++++++++++++++++++++++ libs/ardour/automatable.cc | 17 +------ libs/ardour/lv2_plugin.cc | 45 +++++++++-------- 10 files changed, 175 insertions(+), 97 deletions(-) create mode 100644 libs/ardour/ardour/value_as_string.h diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index c1549ecab3..2e9f988bfa 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -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 al, + const ParameterDescriptor& desc, Evoral::TimeConverter* 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; diff --git a/gtk2_ardour/automation_line.h b/gtk2_ardour/automation_line.h index 02c67d0dcf..a18f93d9ae 100644 --- a/gtk2_ardour/automation_line.h +++ b/gtk2_ardour/automation_line.h @@ -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 al, + const ARDOUR::ParameterDescriptor& desc, Evoral::TimeConverter* 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; }; diff --git a/gtk2_ardour/automation_time_axis.cc b/gtk2_ardour/automation_time_axis.cc index a49bcf0086..3d2773b879 100644 --- a/gtk2_ardour/automation_time_axis.cc +++ b/gtk2_ardour/automation_time_axis.cc @@ -250,7 +250,8 @@ AutomationTimeAxisView::AutomationTimeAxisView ( ARDOUR::EventTypeMap::instance().to_symbol(_parameter), *this, *_canvas_display, - _control->alist() + _control->alist(), + _control->desc() ) ); diff --git a/gtk2_ardour/generic_pluginui.cc b/gtk2_ardour/generic_pluginui.cc index fb35882b34..a4de4fd75e 100644 --- a/gtk2_ardour/generic_pluginui.cc +++ b/gtk2_ardour/generic_pluginui.cc @@ -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)); diff --git a/gtk2_ardour/midi_automation_line.cc b/gtk2_ardour/midi_automation_line.cc index e5f30493d7..30bb37c5b0 100644 --- a/gtk2_ardour/midi_automation_line.cc +++ b/gtk2_ardour/midi_automation_line.cc @@ -34,7 +34,7 @@ MidiAutomationLine::MidiAutomationLine ( boost::shared_ptr region, Evoral::Parameter parameter, Evoral::TimeConverter* converter) - : AutomationLine (name, tav, parent, list, converter) + : AutomationLine (name, tav, parent, list, parameter, converter) , _region (region) , _parameter (parameter) { diff --git a/gtk2_ardour/region_gain_line.cc b/gtk2_ardour/region_gain_line.cc index 43cd0e5140..b010efc04c 100644 --- a/gtk2_ardour/region_gain_line.cc +++ b/gtk2_ardour/region_gain_line.cc @@ -38,7 +38,7 @@ using namespace ARDOUR; using namespace PBD; AudioRegionGainLine::AudioRegionGainLine (const string & name, AudioRegionView& r, ArdourCanvas::Container& parent, boost::shared_ptr 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 diff --git a/libs/ardour/ardour/parameter_descriptor.h b/libs/ardour/ardour/parameter_descriptor.h index 8916f081a3..1576230b8f 100644 --- a/libs/ardour/ardour/parameter_descriptor.h +++ b/libs/ardour/ardour/parameter_descriptor.h @@ -33,24 +33,34 @@ typedef std::map 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 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 diff --git a/libs/ardour/ardour/value_as_string.h b/libs/ardour/ardour/value_as_string.h new file mode 100644 index 0000000000..6c17ace5d3 --- /dev/null +++ b/libs/ardour/ardour/value_as_string.h @@ -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 + +#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__ */ diff --git a/libs/ardour/automatable.cc b/libs/ardour/automatable.cc index 466899ce48..b4d957c8b6 100644 --- a/libs/ardour/automatable.cc +++ b/libs/ardour/automatable.cc @@ -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 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()); } diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index cf33c22424..dca91fd646 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -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); -- cgit v1.2.3