Display gain and midiNote plugin parameters/properties nicely.
authorDavid Robillard <d@drobilla.net>
Sun, 2 Nov 2014 06:29:33 +0000 (01:29 -0500)
committerDavid Robillard <d@drobilla.net>
Sun, 2 Nov 2014 07:10:24 +0000 (02:10 -0500)
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
gtk2_ardour/automation_line.h
gtk2_ardour/automation_time_axis.cc
gtk2_ardour/generic_pluginui.cc
gtk2_ardour/midi_automation_line.cc
gtk2_ardour/region_gain_line.cc
libs/ardour/ardour/parameter_descriptor.h
libs/ardour/ardour/value_as_string.h [new file with mode: 0644]
libs/ardour/automatable.cc
libs/ardour/lv2_plugin.cc

index c1549ecab3f6ae26b565d0e168a5102e2201f7ae..2e9f988bfa3321feb9d72b363469f15c65912146 100644 (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;
index 02c67d0dcf4abd630dffb58952c9f5453d3a1d1d..a18f93d9ae164ff742b095e836d528aada0f1afa 100644 (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;
 };
 
index a49bcf00868f9ee333abf1ba82e1771038f18de1..3d2773b87965edf77a057fcad8e7480f25c498d6 100644 (file)
@@ -250,7 +250,8 @@ AutomationTimeAxisView::AutomationTimeAxisView (
                                ARDOUR::EventTypeMap::instance().to_symbol(_parameter),
                                *this,
                                *_canvas_display,
-                               _control->alist()
+                               _control->alist(),
+                               _control->desc()
                                )
                        );
 
index fb35882b348e41a8e9a0990d43de00746594b94e..a4de4fd75ef63476aed05de9a98c07e7174adaea 100644 (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));
index e5f30493d7e7860528efcbef44228d0cc6ea2ceb..30bb37c5b048fc83faf59ff7f40efad9c9882514 100644 (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)
 {
index 43cd0e5140c3d0f7362d10cbf37c11081d41b6d7..b010efc04c89e33ca00b770ab55858bbcac4570b 100644 (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
index 8916f081a38be3c4cc3f9fc1683e4c6b62aee271..1576230b8fb3427f81ef25aa02d5a70f1656efce 100644 (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
diff --git a/libs/ardour/ardour/value_as_string.h b/libs/ardour/ardour/value_as_string.h
new file mode 100644 (file)
index 0000000..6c17ace
--- /dev/null
@@ -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__ */
index 466899ce48e97d83a0da361ba2fd1bdde5d58d2e..b4d957c8b6862a173d92f9d3f38cb1aba0f22d38 100644 (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());
 }
index cf33c2242492ce975e37a6492fd6bfd59ac50f83..dca91fd646b857cf8ff10cc63d887aa1bd061fb8 100644 (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);