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);