summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtk2_ardour/ardour3_ui_default.conf.in2
-rw-r--r--gtk2_ardour/ardour_button.h2
-rw-r--r--gtk2_ardour/ardour_display.cc149
-rw-r--r--gtk2_ardour/ardour_display.h62
-rw-r--r--gtk2_ardour/ardour_knob.cc458
-rw-r--r--gtk2_ardour/ardour_knob.h97
-rw-r--r--gtk2_ardour/monitor_section.cc125
-rw-r--r--gtk2_ardour/monitor_section.h15
-rw-r--r--gtk2_ardour/wscript2
-rw-r--r--libs/ardour/amp.cc13
-rw-r--r--libs/ardour/ardour/amp.h2
-rw-r--r--libs/ardour/ardour/monitor_processor.h11
-rw-r--r--libs/ardour/ardour/proxy_controllable.h9
-rw-r--r--libs/canvas/canvas/utils.h4
-rw-r--r--libs/canvas/utils.cc33
-rw-r--r--libs/pbd/pbd/controllable.h20
16 files changed, 970 insertions, 34 deletions
diff --git a/gtk2_ardour/ardour3_ui_default.conf.in b/gtk2_ardour/ardour3_ui_default.conf.in
index a91aec32bf..d9f3a26a52 100644
--- a/gtk2_ardour/ardour3_ui_default.conf.in
+++ b/gtk2_ardour/ardour3_ui_default.conf.in
@@ -180,7 +180,7 @@
<Option name="waveform fill" value="ffffffff"/>
<Option name="zero line" value="7f7f7fe0"/>
<Option name="zoom rect" value="c6d1b26d"/>
- <Option name="monitor knob" value="329edfff"/>
+ <Option name="monitor knob" value="555050ff"/>
<Option name="button border" value="000000f0"/>
<Option name="border color" value="00000000"/>
<Option name="processor prefader: fill start" value="873c3cff"/>
diff --git a/gtk2_ardour/ardour_button.h b/gtk2_ardour/ardour_button.h
index 7b8d2a2372..260e0b21ab 100644
--- a/gtk2_ardour/ardour_button.h
+++ b/gtk2_ardour/ardour_button.h
@@ -115,7 +115,7 @@ class ArdourButton : public CairoWidget , public Gtkmm2ext::Activatable
void controllable_changed ();
PBD::ScopedConnection watch_connection;
- private:
+ protected:
Glib::RefPtr<Pango::Layout> _layout;
Glib::RefPtr<Gdk::Pixbuf> _pixbuf;
std::string _text;
diff --git a/gtk2_ardour/ardour_display.cc b/gtk2_ardour/ardour_display.cc
new file mode 100644
index 0000000000..ef845258ac
--- /dev/null
+++ b/gtk2_ardour/ardour_display.cc
@@ -0,0 +1,149 @@
+/*
+ Copyright (C) 2014 Paul Davis
+
+ 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.
+
+*/
+
+#include <iostream>
+#include <cmath>
+#include <algorithm>
+
+#include <pangomm/layout.h>
+
+#include "pbd/compose.h"
+#include "pbd/error.h"
+#include "pbd/stacktrace.h"
+
+#include "gtkmm2ext/utils.h"
+#include "gtkmm2ext/rgb_macros.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/keyboard.h"
+
+#include "ardour/rc_configuration.h" // for widget prelight preference
+
+#include "ardour_display.h"
+#include "ardour_ui.h"
+#include "global_signals.h"
+
+#include "i18n.h"
+
+using namespace Gtkmm2ext;
+using namespace Gdk;
+using namespace Gtk;
+using namespace Glib;
+using namespace PBD;
+using std::max;
+using std::min;
+using namespace std;
+
+
+ArdourDisplay::ArdourDisplay (Element e)
+{
+ signal_button_press_event().connect (sigc::mem_fun(*this, &ArdourDisplay::on_mouse_pressed));
+
+ add_elements(e);
+ add_elements(ArdourButton::Menu);
+ add_elements(ArdourButton::Text);
+}
+
+ArdourDisplay::~ArdourDisplay ()
+{
+}
+
+bool
+ArdourDisplay::on_mouse_pressed (GdkEventButton*)
+{
+ _menu.popup (1, gtk_get_current_event_time());
+ return true;
+}
+
+bool
+ArdourDisplay::on_scroll_event (GdkEventScroll* ev)
+{
+ /* mouse wheel */
+
+ float scale = 1.0;
+ if (ev->state & Keyboard::GainFineScaleModifier) {
+ if (ev->state & Keyboard::GainExtraFineScaleModifier) {
+ scale *= 0.01;
+ } else {
+ scale *= 0.10;
+ }
+ }
+
+ boost::shared_ptr<PBD::Controllable> c = binding_proxy.get_controllable();
+ if (c) {
+ float val = c->get_interface();
+
+ if ( ev->direction == GDK_SCROLL_UP )
+ val += 0.05 * scale; //by default, we step in 1/20ths of the knob travel
+ else
+ val -= 0.05 * scale;
+
+ c->set_interface(val);
+ }
+
+ return true;
+}
+
+
+void
+ArdourDisplay::add_controllable_preset (const char *txt, float val)
+{
+ using namespace Menu_Helpers;
+
+ MenuList& items = _menu.items ();
+
+ items.push_back (MenuElem (txt, sigc::bind (sigc::mem_fun(*this, &ArdourDisplay::handle_controllable_preset), val)));
+}
+
+
+void
+ArdourDisplay::handle_controllable_preset (float p)
+{
+ boost::shared_ptr<PBD::Controllable> c = binding_proxy.get_controllable();
+
+ if (!c) return;
+
+ c->set_user(p);
+}
+
+
+void
+ArdourDisplay::set_controllable (boost::shared_ptr<Controllable> c)
+{
+ watch_connection.disconnect (); //stop watching the old controllable
+
+ if (!c) return;
+
+ binding_proxy.set_controllable (c);
+
+ c->Changed.connect (watch_connection, invalidator(*this), boost::bind (&ArdourDisplay::controllable_changed, this), gui_context());
+
+ controllable_changed();
+}
+
+void
+ArdourDisplay::controllable_changed ()
+{
+ boost::shared_ptr<PBD::Controllable> c = binding_proxy.get_controllable();
+
+ if (!c) return;
+
+ set_text(c->get_user_string());
+
+ set_dirty();
+}
diff --git a/gtk2_ardour/ardour_display.h b/gtk2_ardour/ardour_display.h
new file mode 100644
index 0000000000..90bd61b834
--- /dev/null
+++ b/gtk2_ardour/ardour_display.h
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2014 Paul Davis
+
+ 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 __gtk2_ardour_ardour_display_h__
+#define __gtk2_ardour_ardour_display_h__
+
+#include <list>
+#include <stdint.h>
+
+#include <gtkmm/action.h>
+#include <gtkmm/menu.h>
+#include <gtkmm/menuitem.h>
+
+
+#include "ardour_button.h"
+
+class ArdourDisplay : public ArdourButton
+{
+ public:
+
+ ArdourDisplay (Element e = default_elements);
+ virtual ~ArdourDisplay ();
+
+ boost::shared_ptr<PBD::Controllable> get_controllable() { return binding_proxy.get_controllable(); }
+ void set_controllable (boost::shared_ptr<PBD::Controllable> c);
+
+ bool on_mouse_pressed (GdkEventButton*); //mousedown will pop up our preset menu
+// bool on_button_press_event (GdkEventButton*);
+// bool on_button_release_event (GdkEventButton*);
+ bool on_scroll_event (GdkEventScroll* ev);
+// bool on_motion_notify_event (GdkEventMotion *ev) ;
+
+ void add_controllable_preset (const char*, float);
+ void handle_controllable_preset (float p);
+
+ void controllable_changed ();
+ PBD::ScopedConnection watch_connection;
+
+ private:
+ Gtk::Menu _menu;
+
+ bool _hovering;
+ bool _grabbed;
+ float _grabbed_y;
+};
+
+#endif /* __gtk2_ardour_ardour_menu_h__ */
diff --git a/gtk2_ardour/ardour_knob.cc b/gtk2_ardour/ardour_knob.cc
new file mode 100644
index 0000000000..a125588db7
--- /dev/null
+++ b/gtk2_ardour/ardour_knob.cc
@@ -0,0 +1,458 @@
+/*
+ Copyright (C) 2010 Paul Davis
+
+ 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.
+
+*/
+
+#include <iostream>
+#include <cmath>
+#include <algorithm>
+
+#include <pangomm/layout.h>
+
+#include "pbd/compose.h"
+#include "pbd/error.h"
+#include "pbd/stacktrace.h"
+
+#include "gtkmm2ext/utils.h"
+#include "gtkmm2ext/rgb_macros.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/keyboard.h"
+
+#include "ardour/rc_configuration.h" // for widget prelight preference
+
+#include "ardour_knob.h"
+#include "ardour_ui.h"
+#include "global_signals.h"
+
+#include "canvas/utils.h"
+
+#include "i18n.h"
+
+using namespace Gtkmm2ext;
+using namespace Gdk;
+using namespace Gtk;
+using namespace Glib;
+using namespace PBD;
+using std::max;
+using std::min;
+using namespace std;
+
+ArdourKnob::Element ArdourKnob::default_elements = ArdourKnob::Element (ArdourKnob::Arc);
+
+ArdourKnob::ArdourKnob (Element e)
+ : _elements (e)
+ , _hovering (false)
+ , _grabbed (false)
+{
+ ARDOUR_UI_UTILS::ColorsChanged.connect (sigc::mem_fun (*this, &ArdourKnob::color_handler));
+}
+
+ArdourKnob::~ArdourKnob()
+{
+}
+
+void
+ArdourKnob::render (cairo_t* cr, cairo_rectangle_t *)
+{
+ cairo_pattern_t* shade_pattern;
+
+ float width = get_width();
+ float height = get_height();
+
+ const float scale = min(width, height);
+ const float pointer_thickness = 3.0 * (scale/80); //(if the knob is 80 pixels wide, we want a 3-pix line on it)
+
+ float start_angle = ((180 - 65) * G_PI) / 180;
+ float end_angle = ((360 + 65) * G_PI) / 180;
+ float value_angle = start_angle + (_val * (end_angle - start_angle));
+
+ float value_x = cos (value_angle);
+ float value_y = sin (value_angle);
+
+ cairo_set_antialias( cr, CAIRO_ANTIALIAS_BEST );
+
+ float xc = 0.5 + width/ 2.0;
+ float yc = 0.5 + height/ 2.0;
+
+ cairo_translate (cr, xc, yc); //after this, everything is based on the center of the knob
+
+ //get the knob color from the theme
+ ArdourCanvas::Color knob_color = ARDOUR_UI::config()->color_by_name (string_compose ("%1", get_name()));
+
+ float center_radius = 0.48*scale;
+
+ bool arc = (_elements & Arc)==Arc;
+ bool bevel = (_elements & Bevel)==Bevel;
+ bool flat = ARDOUR_UI::config()->get_flat_buttons();
+
+ if ( arc ) {
+ center_radius = scale*0.25;
+
+ float inner_progress_radius = scale*0.25;
+ float outer_progress_radius = scale*0.48;
+ float progress_width = (outer_progress_radius-inner_progress_radius);
+ float progress_radius = inner_progress_radius + progress_width/2.0;
+
+ float start_angle_x = cos (start_angle);
+ float start_angle_y = sin (start_angle);
+ float end_angle_x = cos (end_angle);
+ float end_angle_y = sin (end_angle);
+
+ //dark arc background
+ cairo_set_source_rgb (cr, 0.3, 0.3, 0.3 );
+ cairo_set_line_width (cr, progress_width);
+ cairo_arc (cr, 0, 0, progress_radius, start_angle, end_angle);
+ cairo_stroke (cr);
+
+ //look up the arc colors from the config
+ double red_start, green_start, blue_start, unused;
+ ArdourCanvas::Color arc_start_color = ARDOUR_UI::config()->color_by_name ( "processor fader: fill start");
+ ArdourCanvas::color_to_rgba( arc_start_color, red_start, green_start, blue_start, unused );
+ double red_end, green_end, blue_end;
+ ArdourCanvas::Color arc_end_color = ARDOUR_UI::config()->color_by_name ( "processor fader: fill end" );
+ ArdourCanvas::color_to_rgba( arc_end_color, red_end, green_end, blue_end, unused );
+
+ //vary the arc color over the travel of the knob
+ float r = (1.0-_val) * red_end + _val * red_start;
+ float g = (1.0-_val) * green_end + _val * green_start;
+ float b = (1.0-_val) * blue_end + _val * blue_start;
+
+ //draw the arc
+ cairo_set_source_rgb (cr, r,g,b);
+ cairo_set_line_width (cr, progress_width);
+ cairo_arc (cr, 0, 0, progress_radius, start_angle, value_angle);
+ cairo_stroke (cr);
+
+ //shade the arc
+ if (!flat) {
+ shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the pattern from our centerpoint
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.15);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.5, 1,1,1, 0.0);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1,1,1, 0.0);
+ cairo_set_source (cr, shade_pattern);
+ cairo_arc (cr, 0, 0, outer_progress_radius-1, 0, 2.0*G_PI);
+ cairo_fill (cr);
+ cairo_pattern_destroy (shade_pattern);
+ }
+
+ //black border
+ cairo_set_source_rgb (cr, 0, 0, 0 );
+ cairo_set_line_width (cr, 1.0);
+ cairo_move_to (cr, (outer_progress_radius * start_angle_x), (outer_progress_radius * start_angle_y));
+ cairo_line_to (cr, (inner_progress_radius * start_angle_x), (inner_progress_radius * start_angle_y));
+ cairo_stroke (cr);
+ cairo_move_to (cr, (outer_progress_radius * end_angle_x), (outer_progress_radius * end_angle_y));
+ cairo_line_to (cr, (inner_progress_radius * end_angle_x), (inner_progress_radius * end_angle_y));
+ cairo_stroke (cr);
+ cairo_arc (cr, 0, 0, outer_progress_radius, start_angle, end_angle);
+ cairo_stroke (cr);
+ }
+
+ if (!flat) {
+ //knob shadow
+ cairo_save(cr);
+ cairo_translate(cr, pointer_thickness+1, pointer_thickness+1 );
+ cairo_set_source_rgba (cr, 0, 0, 0, 0.1 );
+ cairo_arc (cr, 0, 0, center_radius-1, 0, 2.0*G_PI);
+ cairo_fill (cr);
+ cairo_restore(cr);
+
+ //black border
+ cairo_set_source_rgb (cr, 0, 0, 0 );
+ cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
+ cairo_stroke (cr);
+
+ //inner circle
+ ArdourCanvas::set_source_rgba(cr, knob_color);
+ cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
+ cairo_fill (cr);
+
+ //gradient
+ if (bevel) {
+ //knob gradient
+ shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the gradient from our centerpoint
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.2, 1,1,1, 0.2);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.8, 0,0,0, 0.2);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.2);
+ cairo_set_source (cr, shade_pattern);
+ cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
+ cairo_fill (cr);
+ cairo_pattern_destroy (shade_pattern);
+
+ //flat top over beveled edge
+ ArdourCanvas::set_source_rgb_a (cr, knob_color, 0.5 );
+ cairo_arc (cr, 0, 0, center_radius-pointer_thickness, 0, 2.0*G_PI);
+ cairo_fill (cr);
+ } else {
+ //radial gradient
+ shade_pattern = cairo_pattern_create_radial ( -center_radius, -center_radius, 1, -center_radius, -center_radius, center_radius*2.5 ); //note we have to offset the gradient from our centerpoint
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2);
+ cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.3);
+ cairo_set_source (cr, shade_pattern);
+ cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
+ cairo_fill (cr);
+ cairo_pattern_destroy (shade_pattern);
+ }
+
+ } else {
+ //inner circle
+ ArdourCanvas::set_source_rgba(cr, knob_color);
+ cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
+ cairo_fill (cr);
+ }
+
+
+ //black knob border
+ cairo_set_line_width (cr, 1);
+ cairo_set_source_rgba (cr, 0,0,0, 1 );
+ cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
+ cairo_stroke (cr);
+
+ //line shadow
+ if (!flat) {
+ cairo_save(cr);
+ cairo_translate(cr, 2, 2 );
+ cairo_set_source_rgba (cr, 0,0,0,0.5 );
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_width (cr, pointer_thickness);
+ cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y));
+ cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
+ cairo_stroke (cr);
+ cairo_restore(cr);
+ }
+
+ //line
+ cairo_set_source_rgba (cr, 1,1,1, 1 );
+ cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
+ cairo_set_line_width (cr, pointer_thickness);
+ cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y));
+ cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
+ cairo_stroke (cr);
+
+ //highlight if grabbed or if mouse is hovering over me
+ if ( _grabbed || (_hovering && ARDOUR::Config->get_widget_prelight() ) ) {
+ cairo_set_source_rgba (cr, 1,1,1, 0.12 );
+ cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
+ cairo_fill (cr);
+ }
+
+ cairo_identity_matrix(cr);
+}
+
+void
+ArdourKnob::on_size_request (Gtk::Requisition* req)
+{
+ CairoWidget::on_size_request (req);
+
+ //perhaps render the knob base into a cached image here?
+}
+
+bool
+ArdourKnob::on_scroll_event (GdkEventScroll* ev)
+{
+ /* mouse wheel */
+
+ float scale = 0.05; //by default, we step in 1/20ths of the knob travel
+ if (ev->state & Keyboard::GainFineScaleModifier) {
+ if (ev->state & Keyboard::GainExtraFineScaleModifier) {
+ scale *= 0.01;
+ } else {
+ scale *= 0.10;
+ }
+ }
+
+ boost::shared_ptr<PBD::Controllable> c = binding_proxy.get_controllable();
+ if (c) {
+ float val = c->get_interface();
+
+ if ( ev->direction == GDK_SCROLL_UP )
+ val += scale;
+ else
+ val -= scale;
+
+ c->set_interface(val);
+ }
+
+ return true;
+}
+
+bool
+ArdourKnob::on_motion_notify_event (GdkEventMotion *ev)
+{
+ //scale the adjustment based on keyboard modifiers
+ float scale = 0.0025;
+ if (ev->state & Keyboard::GainFineScaleModifier) {
+ if (ev->state & Keyboard::GainExtraFineScaleModifier) {
+ scale *= 0.01;
+ } else {
+ scale *= 0.10;
+ }
+ }
+
+ //calculate the travel of the mouse
+ int y_delta = 0;
+ if (ev->state & Gdk::BUTTON1_MASK) {
+ y_delta = _grabbed_y - ev->y;
+ _grabbed_y = ev->y;
+ if (y_delta == 0) return TRUE;
+ }
+
+ //step the value of the controllable
+ boost::shared_ptr<PBD::Controllable> c = binding_proxy.get_controllable();
+ if (c) {
+ float val = c->get_interface();
+ val += y_delta * scale;
+ c->set_interface(val);
+ }
+
+ return true;
+}
+
+bool
+ArdourKnob::on_button_press_event (GdkEventButton *ev)
+{
+ _grabbed_y = ev->y;
+ _grabbed = true;
+
+ set_active_state (Gtkmm2ext::ExplicitActive);
+
+ if (binding_proxy.button_press_handler (ev)) {
+ return true;
+ }
+
+ return false;
+}
+
+bool
+ArdourKnob::on_button_release_event (GdkEventButton *ev)
+{
+ _grabbed = false;
+ unset_active_state ();
+
+ return false;
+}
+
+void
+ArdourKnob::color_handler ()
+{
+ set_dirty ();
+}
+
+void
+ArdourKnob::on_size_allocate (Allocation& alloc)
+{
+ CairoWidget::on_size_allocate (alloc);
+}
+
+void
+ArdourKnob::set_controllable (boost::shared_ptr<Controllable> c)
+{
+ watch_connection.disconnect (); //stop watching the old controllable
+
+ if (!c) return;
+
+ binding_proxy.set_controllable (c);
+
+ c->Changed.connect (watch_connection, invalidator(*this), boost::bind (&ArdourKnob::controllable_changed, this), gui_context());
+
+ controllable_changed();
+}
+
+void
+ArdourKnob::controllable_changed ()
+{
+ _val = binding_proxy.get_controllable()->get_interface(); //% of knob travel
+
+ _val = min( max(0.0f, _val), 1.0f); //range check
+
+ set_dirty();
+}
+
+void
+ArdourKnob::on_style_changed (const RefPtr<Gtk::Style>&)
+{
+ set_dirty ();
+}
+
+void
+ArdourKnob::on_name_changed ()
+{
+ set_dirty ();
+}
+
+
+void
+ArdourKnob::set_active_state (Gtkmm2ext::ActiveState s)
+{
+ if (_active_state != s)
+ CairoWidget::set_active_state (s);
+}
+
+void
+ArdourKnob::set_visual_state (Gtkmm2ext::VisualState s)
+{
+ if (_visual_state != s)
+ CairoWidget::set_visual_state (s);
+}
+
+
+bool
+ArdourKnob::on_focus_in_event (GdkEventFocus* ev)
+{
+ set_dirty ();
+ return CairoWidget::on_focus_in_event (ev);
+}
+
+bool
+ArdourKnob::on_focus_out_event (GdkEventFocus* ev)
+{
+ set_dirty ();
+ return CairoWidget::on_focus_out_event (ev);
+}
+
+bool
+ArdourKnob::on_enter_notify_event (GdkEventCrossing* ev)
+{
+ _hovering = true;
+
+ set_dirty ();
+
+ return CairoWidget::on_enter_notify_event (ev);
+}
+
+bool
+ArdourKnob::on_leave_notify_event (GdkEventCrossing* ev)
+{
+ _hovering = false;
+
+ set_dirty ();
+
+ return CairoWidget::on_leave_notify_event (ev);
+}
+
+void
+ArdourKnob::set_elements (Element e)
+{
+ _elements = e;
+}
+
+void
+ArdourKnob::add_elements (Element e)
+{
+ _elements = (ArdourKnob::Element) (_elements | e);
+}
diff --git a/gtk2_ardour/ardour_knob.h b/gtk2_ardour/ardour_knob.h
new file mode 100644
index 0000000000..1a318a21dc
--- /dev/null
+++ b/gtk2_ardour/ardour_knob.h
@@ -0,0 +1,97 @@
+/*
+ Copyright (C) 2014 Paul Davis
+
+ 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 __gtk2_ardour_ardour_knob_h__
+#define __gtk2_ardour_ardour_knob_h__
+
+#include <list>
+#include <stdint.h>
+
+#include <gtkmm/action.h>
+
+#include "pbd/signals.h"
+#include "gtkmm2ext/binding_proxy.h"
+#include "gtkmm2ext/activatable.h"
+#include "gtkmm2ext/cairo_widget.h"
+
+class ArdourKnob : public CairoWidget , public Gtkmm2ext::Activatable
+{
+public:
+
+ enum Element {
+ Arc = 0x1,
+ Bevel = 0x2,
+ unused2 = 0x4,
+ unused3 = 0x8,
+ unused4 = 0x10,
+ unused5 = 0x20,
+ };
+
+ ArdourKnob (Element e = default_elements);
+ virtual ~ArdourKnob ();
+
+ void set_active_state (Gtkmm2ext::ActiveState);
+ void set_visual_state (Gtkmm2ext::VisualState);
+
+ Element elements() const { return _elements; }
+ void set_elements (Element);
+ void add_elements (Element);
+ static Element default_elements;
+
+ boost::shared_ptr<PBD::Controllable> get_controllable() { return binding_proxy.get_controllable(); }
+ void set_controllable (boost::shared_ptr<PBD::Controllable> c);
+
+ bool on_button_press_event (GdkEventButton*);
+ bool on_button_release_event (GdkEventButton*);
+ bool on_scroll_event (GdkEventScroll* ev);
+ bool on_motion_notify_event (GdkEventMotion *ev) ;
+
+ void color_handler ();
+
+ protected:
+ void render (cairo_t *, cairo_rectangle_t *);
+ void on_size_request (Gtk::Requisition* req);
+ void on_size_allocate (Gtk::Allocation&);
+ void on_style_changed (const Glib::RefPtr<Gtk::Style>&);
+ void on_name_changed ();
+ bool on_enter_notify_event (GdkEventCrossing*);
+ bool on_leave_notify_event (GdkEventCrossing*);
+ bool on_focus_in_event (GdkEventFocus*);
+ bool on_focus_out_event (GdkEventFocus*);
+
+ void controllable_changed ();
+ PBD::ScopedConnection watch_connection;
+
+
+ private:
+ Element _elements;
+
+ BindingProxy binding_proxy;
+
+ bool _hovering;
+ bool _grabbed;
+ float _grabbed_y;
+
+ float _val; //percent of knob travel
+
+ void action_sensitivity_changed ();
+ void action_visibility_changed ();
+ void action_tooltip_changed ();
+};
+
+#endif /* __gtk2_ardour_ardour_knob_h__ */
diff --git a/gtk2_ardour/monitor_section.cc b/gtk2_ardour/monitor_section.cc
index a816dd397d..3f7ebfaa6e 100644
--- a/gtk2_ardour/monitor_section.cc
+++ b/gtk2_ardour/monitor_section.cc
@@ -27,6 +27,9 @@
#include "gtkmm2ext/actions.h"
#include "gtkmm2ext/motionfeedback.h"
+#include <gtkmm/menu.h>
+#include <gtkmm/menuitem.h>
+
#include "ardour/monitor_processor.h"
#include "ardour/route.h"
@@ -60,6 +63,10 @@ MonitorSection::MonitorSection (Session* s)
, dim_control (0)
, solo_boost_control (0)
, solo_cut_control (0)
+ , gain_display (0)
+ , dim_display (0)
+ , solo_boost_display (0)
+ , solo_cut_display (0)
, solo_in_place_button (_("SiP"), ArdourButton::led_default_elements)
, afl_button (_("AFL"), ArdourButton::led_default_elements)
, pfl_button (_("PFL"), ArdourButton::led_default_elements)
@@ -67,6 +74,9 @@ MonitorSection::MonitorSection (Session* s)
, solo_mute_override_button (ArdourButton::led_default_elements)
, _inhibit_solo_model_update (false)
{
+
+ using namespace Menu_Helpers;
+
Glib::RefPtr<Action> act;
if (!monitor_actions) {
@@ -141,9 +151,19 @@ MonitorSection::MonitorSection (Session* s)
/* Solo Boost */
- solo_boost_control = new VolumeController (little_knob_pixbuf, boost::shared_ptr<Controllable>(), 0.0, 0.01, 0.1, true, 30, 30, true);
- ARDOUR_UI::instance()->tooltips().set_tip (*solo_boost_control, _("Gain increase for soloed signals (0dB is normal)"));
-
+ solo_boost_control = new ArdourKnob ();
+ solo_boost_control->set_name("monitor knob");
+ solo_boost_control->set_size_request(40,40);
+ ARDOUR_UI::instance()->tooltips().set_tip (*solo_boost_control, _("Gain increase for soloed signals (0dB is normal)"));
+
+ solo_boost_display = new ArdourDisplay ();
+ solo_boost_display->set_name("monitor section cut");
+ solo_boost_display->set_size_request(80,20);
+ solo_boost_display->add_controllable_preset("0dB", 0.0);
+ solo_boost_display->add_controllable_preset("3 dB", 3.0);
+ solo_boost_display->add_controllable_preset("6 dB", 6.0);
+ solo_boost_display->add_controllable_preset("10 dB", 10.0);
+
HBox* solo_packer = manage (new HBox);
solo_packer->set_spacing (6);
solo_packer->show ();
@@ -151,44 +171,69 @@ MonitorSection::MonitorSection (Session* s)
spin_label = manage (new Label (_("Solo Boost")));
spin_packer = manage (new VBox);
spin_packer->show ();
- spin_packer->set_spacing (6);
- spin_packer->pack_start (*solo_boost_control, false, false);
+ spin_packer->set_spacing (3);
spin_packer->pack_start (*spin_label, false, false);
+ spin_packer->pack_start (*solo_boost_control, false, false);
+ spin_packer->pack_start (*solo_boost_display, false, false);
solo_packer->pack_start (*spin_packer, true, false);
/* Solo (SiP) cut */
- solo_cut_control = new VolumeController (little_knob_pixbuf, boost::shared_ptr<Controllable>(), 0.0, 0.1, 0.5, true, 30, 30, true);
- ARDOUR_UI::instance()->tooltips().set_tip (*solo_cut_control, _("Gain reduction non-soloed signals\nA value above -inf dB causes \"solo-in-front\""));
-
+ solo_cut_control = new ArdourKnob ();
+ solo_cut_control->set_name ("monitor knob");
+ solo_cut_control->set_size_request (40,40);
+ ARDOUR_UI::instance()->tooltips().set_tip (*solo_cut_control, _("Gain reduction non-soloed signals\nA value above -inf dB causes \"solo-in-front\""));
+
+ solo_cut_display = new ArdourDisplay ();
+ solo_cut_display->set_name("monitor section cut");
+ solo_cut_display->set_size_request(80,20);
+ solo_cut_display->add_controllable_preset("0dB", 0.0);
+ solo_cut_display->add_controllable_preset("-6 dB", -6.0);
+ solo_cut_display->add_controllable_preset("-12 dB", -12.0);
+ solo_cut_display->add_controllable_preset("-20 dB", -20.0);
+
spin_label = manage (new Label (_("SiP Cut")));
spin_packer = manage (new VBox);
spin_packer->show ();
- spin_packer->set_spacing (6);
- spin_packer->pack_start (*solo_cut_control, false, false);
+ spin_packer->set_spacing (3);
spin_packer->pack_start (*spin_label, false, false);
+ spin_packer->pack_start (*solo_cut_control, false, false);
+ spin_packer->pack_start (*solo_cut_display, false, false);
solo_packer->pack_start (*spin_packer, true, false);
/* Dim */
- dim_control = new VolumeController (little_knob_pixbuf, boost::shared_ptr<Controllable>(), 0.0, 0.01, 0.1, true, 30, 30, true);
- ARDOUR_UI::instance()->tooltips().set_tip (*dim_control, _("Gain reduction to use when dimming monitor outputs"));
-
+ dim_control = new ArdourKnob ();
+ dim_control->set_name ("monitor knob");
+ dim_control->set_size_request (40,40);
+ ARDOUR_UI::instance()->tooltips().set_tip (*dim_control, _("Gain reduction to use when dimming monitor outputs"));
+
+ dim_display = new ArdourDisplay ();
+ dim_display->set_name("monitor section cut");
+ dim_display->set_size_request(80,20);
+ dim_display->add_controllable_preset("0dB", 0.0);
+ dim_display->add_controllable_preset("-3 dB", -3.0);
+ dim_display->add_controllable_preset("-6 dB", -6.0);
+ dim_display->add_controllable_preset("-12 dB", -12.0);
+ dim_display->add_controllable_preset("-20 dB", -20.0);
+ dim_display->add_controllable_preset("-30 dB", -30.0);
+
HBox* dim_packer = manage (new HBox);
dim_packer->show ();
spin_label = manage (new Label (_("Dim")));
spin_packer = manage (new VBox);
spin_packer->show ();
- spin_packer->set_spacing (6);
- spin_packer->pack_start (*dim_control, false, false);
+ spin_packer->set_spacing (3);
spin_packer->pack_start (*spin_label, false, false);
+ spin_packer->pack_start (*dim_control, false, false);
+ spin_packer->pack_start (*dim_display, false, false);
dim_packer->pack_start (*spin_packer, true, false);
- exclusive_solo_button.set_text (_("excl. solo"));
+ exclusive_solo_button.set_text (_("excl. solo"));
exclusive_solo_button.set_name (X_("monitor solo exclusive"));
ARDOUR_UI::instance()->set_tip (&exclusive_solo_button, _("Exclusive solo means that only 1 solo is active at a time"));
@@ -262,14 +307,27 @@ MonitorSection::MonitorSection (Session* s)
/* Gain */
- gain_control = new VolumeController (big_knob_pixbuf, boost::shared_ptr<Controllable>(), 1.0, 0.01, 0.1, true, 80, 80, false);
-
- spin_label = manage (new Label (_("Monitor")));
- spin_packer = manage (new VBox);
+ gain_control = new ArdourKnob ();
+ gain_control->set_name("monitor knob");
+ gain_control->set_size_request(80,80);
+
+ gain_display = new ArdourDisplay ();
+ gain_display->set_name("monitor section cut");
+ gain_display->set_size_request(40,20);
+ gain_display->add_controllable_preset("0dB", 0.0);
+ gain_display->add_controllable_preset("-3 dB", -3.0);
+ gain_display->add_controllable_preset("-6 dB", -6.0);
+ gain_display->add_controllable_preset("-12 dB", -12.0);
+ gain_display->add_controllable_preset("-20 dB", -20.0);
+ gain_display->add_controllable_preset("-30 dB", -30.0);
+
+ spin_label = manage (new Label (_("Monitor")));
+ spin_packer = manage (new VBox);
spin_packer->show ();
- spin_packer->set_spacing (6);
- spin_packer->pack_start (*gain_control, false, false);
+ spin_packer->set_spacing (3);
spin_packer->pack_start (*spin_label, false, false);
+ spin_packer->pack_start (*gain_control, false, false);
+ spin_packer->pack_start (*gain_display, false, false);
lower_packer.pack_start (*spin_packer, true, true);
@@ -317,8 +375,11 @@ MonitorSection::MonitorSection (Session* s)
hpacker.pack_start (vpacker, true, true);
gain_control->show_all ();
+ gain_display->show_all ();
dim_control->show_all ();
+ dim_display->show_all();
solo_boost_control->show_all ();
+ solo_boost_display->show_all();
channel_table.show ();
hpacker.show ();
@@ -351,8 +412,13 @@ MonitorSection::~MonitorSection ()
_channel_buttons.clear ();
delete gain_control;
+ delete gain_display;
delete dim_control;
+ delete dim_display;
delete solo_boost_control;
+ delete solo_boost_display;
+ delete solo_cut_control;
+ delete solo_cut_display;
delete _tearoff;
}
@@ -1064,13 +1130,16 @@ MonitorSection::assign_controllables ()
}
if (_session) {
- solo_cut_control->set_controllable (_session->solo_cut_control());
+ solo_cut_control->set_controllable (_session->solo_cut_control());
+ solo_cut_display->set_controllable (_session->solo_cut_control());
} else {
- solo_cut_control->set_controllable (none);
+ solo_cut_control->set_controllable (none);
+ solo_cut_display->set_controllable (none);
}
if (_route) {
gain_control->set_controllable (_route->gain_control());
+ gain_display->set_controllable (_route->gain_control());
} else {
gain_control->set_controllable (none);
}
@@ -1084,8 +1153,10 @@ MonitorSection::assign_controllables ()
mono_button.set_controllable (_monitor->mono_control());
mono_button.watch ();
- dim_control->set_controllable (_monitor->dim_level_control ());
- solo_boost_control->set_controllable (_monitor->solo_boost_control ());
+ dim_control->set_controllable (_monitor->dim_level_control ());
+ dim_display->set_controllable (_monitor->dim_level_control ());
+ solo_boost_control->set_controllable (_monitor->solo_boost_control ());
+ solo_boost_display->set_controllable (_monitor->solo_boost_control ());
} else {
@@ -1094,7 +1165,9 @@ MonitorSection::assign_controllables ()
mono_button.set_controllable (none);
dim_control->set_controllable (none);
+ dim_display->set_controllable (none);
solo_boost_control->set_controllable (none);
+ solo_boost_display->set_controllable (none);
}
}
diff --git a/gtk2_ardour/monitor_section.h b/gtk2_ardour/monitor_section.h
index f7848a3f01..d1fc7d8da1 100644
--- a/gtk2_ardour/monitor_section.h
+++ b/gtk2_ardour/monitor_section.h
@@ -23,6 +23,8 @@
#include "gtkmm2ext/bindable_button.h"
#include "ardour_button.h"
+#include "ardour_knob.h"
+#include "ardour_display.h"
#include "axis_view.h"
#include "level_meter.h"
#include "route_ui.h"
@@ -74,11 +76,16 @@ class MonitorSection : public RouteUI
typedef std::vector<ChannelButtonSet*> ChannelButtons;
ChannelButtons _channel_buttons;
- VolumeController* gain_control;
- VolumeController* dim_control;
- VolumeController* solo_boost_control;
- VolumeController* solo_cut_control;
+ ArdourKnob* gain_control;
+ ArdourKnob* dim_control;
+ ArdourKnob* solo_boost_control;
+ ArdourKnob* solo_cut_control;
+ ArdourDisplay* gain_display;
+ ArdourDisplay* dim_display;
+ ArdourDisplay* solo_boost_display;
+ ArdourDisplay* solo_cut_display;
+
void populate_buttons ();
void map_state ();
diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript
index 95d359f982..e99d97b516 100644
--- a/gtk2_ardour/wscript
+++ b/gtk2_ardour/wscript
@@ -25,7 +25,9 @@ gtk2_ardour_sources = [
'analysis_window.cc',
'ardour_button.cc',
'ardour_dialog.cc',
+ 'ardour_display.cc',
'ardour_dropdown.cc',
+ 'ardour_knob.cc',
'ardour_ui.cc',
'ardour_ui2.cc',
'ardour_ui_dependents.cc',
diff --git a/libs/ardour/amp.cc b/libs/ardour/amp.cc
index 29032525f2..2265c6de03 100644
--- a/libs/ardour/amp.cc
+++ b/libs/ardour/amp.cc
@@ -426,6 +426,19 @@ Amp::GainControl::internal_to_user (double v) const
return accurate_coefficient_to_dB (v);
}
+double
+Amp::GainControl::user_to_internal (double u) const
+{
+ return dB_to_coefficient (u);
+}
+
+std::string
+Amp::GainControl::get_user_string () const
+{
+ char theBuf[32]; sprintf( theBuf, "%3.1f dB", accurate_coefficient_to_dB (get_value()));
+ return std::string(theBuf);
+}
+
/** Write gain automation for this cycle into the buffer previously passed in to
* set_gain_automation_buffer (if we are in automation playback mode and the
* transport is rolling).
diff --git a/libs/ardour/ardour/amp.h b/libs/ardour/ardour/amp.h
index f6a15666e9..c0e9dbc5b5 100644
--- a/libs/ardour/ardour/amp.h
+++ b/libs/ardour/ardour/amp.h
@@ -90,6 +90,8 @@ public:
double internal_to_interface (double) const;
double interface_to_internal (double) const;
double internal_to_user (double) const;
+ double user_to_internal (double) const;
+ std::string get_user_string () const;
Amp* _amp;
};
diff --git a/libs/ardour/ardour/monitor_processor.h b/libs/ardour/ardour/monitor_processor.h
index 33b3e9c366..2fe108a427 100644
--- a/libs/ardour/ardour/monitor_processor.h
+++ b/libs/ardour/ardour/monitor_processor.h
@@ -32,6 +32,8 @@
#include "ardour/types.h"
#include "ardour/processor.h"
+#include "ardour/dB.h"
+
class XMLNode;
namespace ARDOUR {
@@ -63,6 +65,15 @@ public:
return (float) _value;
}
+ double internal_to_user (double i) const { return accurate_coefficient_to_dB (i);}
+ double user_to_internal (double u) const { return dB_to_coefficient(u) ;}
+
+ std::string get_user_string () const
+ {
+ char theBuf[32]; sprintf( theBuf, "%3.1f dB", accurate_coefficient_to_dB (get_value()));
+ return std::string(theBuf);
+ }
+
double lower () const { return _lower; }
double upper () const { return _upper; }
diff --git a/libs/ardour/ardour/proxy_controllable.h b/libs/ardour/ardour/proxy_controllable.h
index 066f2aac81..522c3a2794 100644
--- a/libs/ardour/ardour/proxy_controllable.h
+++ b/libs/ardour/ardour/proxy_controllable.h
@@ -43,6 +43,15 @@ public:
void set_value (double v) { if (_setter (v)) { Changed(); /* EMIT SIGNAL */ } }
double get_value () const { return _getter (); }
+ double internal_to_user (double i) const { return accurate_coefficient_to_dB (i);}
+ double user_to_internal (double u) const { return dB_to_coefficient(u) ;}
+
+ std::string get_user_string () const
+ {
+ char theBuf[32]; sprintf( theBuf, "%3.1f dB", accurate_coefficient_to_dB (get_value()));
+ return std::string(theBuf);
+ }
+
private:
boost::function1<bool,double> _setter;
boost::function0<double> _getter;
diff --git a/libs/canvas/canvas/utils.h b/libs/canvas/canvas/utils.h
index e269ca215c..947e57cc7c 100644
--- a/libs/canvas/canvas/utils.h
+++ b/libs/canvas/canvas/utils.h
@@ -29,6 +29,10 @@ namespace ArdourCanvas {
extern LIBCANVAS_API Color rgba_to_color (double r, double g, double b, double a);
extern LIBCANVAS_API void set_source_rgba (Cairo::RefPtr<Cairo::Context>, Color);
+ extern LIBCANVAS_API void set_source_rgb_a (Cairo::RefPtr<Cairo::Context>, Color, float alpha); //override the color's alpha
+
+ extern LIBCANVAS_API void set_source_rgba (cairo_t*, Color);
+ extern LIBCANVAS_API void set_source_rgb_a (cairo_t*, Color, float alpha); //override the color's alpha
Distance LIBCANVAS_API distance_to_segment_squared (Duple const & p, Duple const & p1, Duple const & p2, double& t, Duple& at);
diff --git a/libs/canvas/utils.cc b/libs/canvas/utils.cc
index 99516c849b..b7571a7844 100644
--- a/libs/canvas/utils.cc
+++ b/libs/canvas/utils.cc
@@ -154,6 +154,39 @@ ArdourCanvas::set_source_rgba (Cairo::RefPtr<Cairo::Context> context, Color colo
);
}
+void
+ArdourCanvas::set_source_rgb_a (Cairo::RefPtr<Cairo::Context> context, Color color, float alpha)
+{
+ context->set_source_rgba (
+ ((color >> 24) & 0xff) / 255.0,
+ ((color >> 16) & 0xff) / 255.0,
+ ((color >> 8) & 0xff) / 255.0,
+ alpha
+ );
+}
+
+void
+ArdourCanvas::set_source_rgba (cairo_t *cr, Color color)
+{
+ cairo_set_source_rgba ( cr,
+ ((color >> 24) & 0xff) / 255.0,
+ ((color >> 16) & 0xff) / 255.0,
+ ((color >> 8) & 0xff) / 255.0,
+ ((color >> 0) & 0xff) / 255.0
+ );
+}
+
+void
+ArdourCanvas::set_source_rgb_a (cairo_t *cr, Color color, float alpha)
+{
+ cairo_set_source_rgba ( cr,
+ ((color >> 24) & 0xff) / 255.0,
+ ((color >> 16) & 0xff) / 255.0,
+ ((color >> 8) & 0xff) / 255.0,
+ alpha
+ );
+}
+
ArdourCanvas::Distance
ArdourCanvas::distance_to_segment_squared (Duple const & p, Duple const & p1, Duple const & p2, double& t, Duple& at)
{
diff --git a/libs/pbd/pbd/controllable.h b/libs/pbd/pbd/controllable.h
index eb4b7ff142..fb8f79db09 100644
--- a/libs/pbd/pbd/controllable.h
+++ b/libs/pbd/pbd/controllable.h
@@ -60,11 +60,25 @@ class LIBPBD_API Controllable : public PBD::StatefulDestructible {
* but passed to the processor as a linear quantity.
*/
- /** Set `internal' value */
+ /** Get and Set `internal' value */
virtual void set_value (double) = 0;
- /** @return `internal' value */
virtual double get_value (void) const = 0;
+ /** Conversions between `internal', 'interface', and 'user' values */
+ virtual double internal_to_interface (double i) const {return (i-lower())/(upper() - lower());} //by default, the interface range is just a linear interpolation between lower and upper values
+ virtual double interface_to_internal (double i) const {return lower() + i*(upper() - lower());}
+ virtual double internal_to_user (double i) const {return i;} //by default the internal value is the same as the user value
+ virtual double user_to_internal (double i) const {return i;} //by default the internal value is the same as the user value
+
+ /** Get and Set `interface' value (typically, percent of knob travel) */
+ virtual float get_interface() const { return (internal_to_interface(get_value())); }
+ virtual void set_interface (float percent) { percent = std::min( std::max(0.0f, percent), 1.0f); set_value(interface_to_internal(percent)); }
+
+ /** Get and Set `user' value ( dB or milliseconds, etc. This MIGHT be the same as the internal value, but in a few cases it is not ) */
+ virtual float get_user() const { return (internal_to_user(get_value())); }
+ virtual void set_user (float user_v) { set_value(user_to_internal(user_v)); }
+ virtual std::string get_user_string() const { return std::string(); }
+
PBD::Signal0<void> LearningFinished;
static PBD::Signal3<void,PBD::Controllable*,int,int> CreateBinding;
static PBD::Signal1<void,PBD::Controllable*> DeleteBinding;
@@ -99,6 +113,8 @@ class LIBPBD_API Controllable : public PBD::StatefulDestructible {
private:
std::string _name;
+ std::string _units;
+
Flag _flags;
bool _touching;