2 Copyright (C) 2011 Paul Davis
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 #include "ardour/ardour.h"
24 #include "ardour/audioengine.h"
25 #include "ardour/rc_configuration.h"
26 #include "ardour/session.h"
28 #include "gtkmm2ext/keyboard.h"
29 #include "gtkmm2ext/gui_thread.h"
30 #include "gtkmm2ext/cairocell.h"
31 #include "gtkmm2ext/utils.h"
32 #include "gtkmm2ext/rgb_macros.h"
35 #include "ardour_ui.h"
36 #include "rgb_macros.h"
37 #include "shuttle_control.h"
42 using namespace Gtkmm2ext;
43 using namespace ARDOUR;
47 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
52 ShuttleControl::ShuttleControl ()
53 : _controllable (new ShuttleControllable (*this))
54 , binding_proxy (_controllable)
56 ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
60 last_shuttle_request = 0;
61 last_speed_displayed = -99999999;
62 shuttle_grabbed = false;
63 shuttle_speed_on_grab = 0;
65 shuttle_max_speed = 8.0f;
66 shuttle_style_menu = 0;
67 shuttle_unit_menu = 0;
68 shuttle_context_menu = 0;
71 set_flags (CAN_FOCUS);
72 add_events (Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::BUTTON_PRESS_MASK|Gdk::POINTER_MOTION_MASK|Gdk::SCROLL_MASK);
73 set_size_request (85, 20);
74 set_name (X_("ShuttleControl"));
76 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, boost::bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
78 /* gtkmm 2.4: the C++ wrapper doesn't work */
79 g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
80 // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
83 ShuttleControl::~ShuttleControl ()
85 cairo_pattern_destroy (pattern);
86 cairo_pattern_destroy (shine_pattern);
90 ShuttleControl::set_session (Session *s)
92 SessionHandlePtr::set_session (s);
96 _session->add_controllable (_controllable);
98 set_sensitive (false);
103 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
106 cairo_pattern_destroy (pattern);
108 cairo_pattern_destroy (shine_pattern);
112 CairoWidget::on_size_allocate ( alloc);
115 pattern = cairo_pattern_create_linear (0, 0, 0, alloc.get_height());
116 uint32_t col = ARDOUR_UI::config()->get_canvasvar_Shuttle();
118 UINT_TO_RGBA(col, &r, &g, &b, &a);
119 cairo_pattern_add_color_stop_rgb (pattern, 0.0, r/400.0, g/400.0, b/400.0);
120 cairo_pattern_add_color_stop_rgb (pattern, 0.4, r/255.0, g/255.0, b/255.0);
121 cairo_pattern_add_color_stop_rgb (pattern, 1.0, r/512.0, g/512.0, b/512.0);
124 shine_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, 10);
125 cairo_pattern_add_color_stop_rgba (shine_pattern, 0, 1,1,1,0.0);
126 cairo_pattern_add_color_stop_rgba (shine_pattern, 0.2, 1,1,1,0.4);
127 cairo_pattern_add_color_stop_rgba (shine_pattern, 1, 1,1,1,0.1);
131 ShuttleControl::map_transport_state ()
133 float speed = _session->transport_speed ();
135 if (fabs(speed) <= (2*DBL_EPSILON)) {
138 if (Config->get_shuttle_units() == Semitones) {
140 int semi = speed_as_semitones (speed, reverse);
141 shuttle_fract = semitones_as_fract (semi, reverse);
143 shuttle_fract = speed/shuttle_max_speed;
151 ShuttleControl::build_shuttle_context_menu ()
153 using namespace Menu_Helpers;
155 shuttle_context_menu = new Menu();
156 MenuList& items = shuttle_context_menu->items();
158 Menu* speed_menu = manage (new Menu());
159 MenuList& speed_items = speed_menu->items();
161 Menu* units_menu = manage (new Menu);
162 MenuList& units_items = units_menu->items();
163 RadioMenuItem::Group units_group;
165 units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
166 if (Config->get_shuttle_units() == Percentage) {
167 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
169 units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
170 if (Config->get_shuttle_units() == Semitones) {
171 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
173 items.push_back (MenuElem (_("Units"), *units_menu));
175 Menu* style_menu = manage (new Menu);
176 MenuList& style_items = style_menu->items();
177 RadioMenuItem::Group style_group;
179 style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
180 if (Config->get_shuttle_behaviour() == Sprung) {
181 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
183 style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
184 if (Config->get_shuttle_behaviour() == Wheel) {
185 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
188 items.push_back (MenuElem (_("Mode"), *style_menu));
190 RadioMenuItem::Group speed_group;
192 speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
193 if (shuttle_max_speed == 8.0) {
194 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
196 speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
197 if (shuttle_max_speed == 6.0) {
198 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
200 speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
201 if (shuttle_max_speed == 4.0) {
202 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
204 speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
205 if (shuttle_max_speed == 3.0) {
206 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
208 speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
209 if (shuttle_max_speed == 2.0) {
210 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
212 speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
213 if (shuttle_max_speed == 1.5) {
214 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
217 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
222 ShuttleControl::show_shuttle_context_menu ()
224 if (shuttle_context_menu == 0) {
225 build_shuttle_context_menu ();
228 shuttle_context_menu->popup (1, gtk_get_current_event_time());
232 ShuttleControl::set_shuttle_max_speed (float speed)
234 shuttle_max_speed = speed;
238 ShuttleControl::on_button_press_event (GdkEventButton* ev)
244 if (binding_proxy.button_press_handler (ev)) {
248 if (Keyboard::is_context_menu_event (ev)) {
249 show_shuttle_context_menu ();
253 switch (ev->button) {
255 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
256 if (_session->transport_rolling()) {
257 _session->request_transport_speed (1.0);
261 shuttle_grabbed = true;
262 shuttle_speed_on_grab = _session->transport_speed ();
263 mouse_shuttle (ev->x, true);
264 gdk_pointer_grab(ev->window,false,
265 GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
280 ShuttleControl::on_button_release_event (GdkEventButton* ev)
286 switch (ev->button) {
288 if (shuttle_grabbed) {
289 shuttle_grabbed = false;
290 remove_modal_grab ();
291 gdk_pointer_ungrab (GDK_CURRENT_TIME);
293 if (Config->get_shuttle_behaviour() == Sprung) {
294 if (shuttle_speed_on_grab == 0 ) {
295 _session->request_transport_speed (1.0);
297 _session->request_transport_speed (shuttle_speed_on_grab);
299 mouse_shuttle (ev->x, true);
305 if (_session->transport_rolling()) {
306 _session->request_transport_speed (1.0, Config->get_shuttle_behaviour() == Wheel);
320 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
326 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
328 if (!_session || Config->get_shuttle_behaviour() != Wheel) {
332 bool semis = (Config->get_shuttle_units() == Semitones);
334 switch (ev->direction) {
336 case GDK_SCROLL_RIGHT:
338 if (shuttle_fract == 0) {
339 shuttle_fract = semitones_as_fract (1, false);
342 int st = fract_as_semitones (shuttle_fract, rev);
343 shuttle_fract = semitones_as_fract (st + 1, rev);
346 shuttle_fract += 0.00125;
349 case GDK_SCROLL_DOWN:
350 case GDK_SCROLL_LEFT:
352 if (shuttle_fract == 0) {
353 shuttle_fract = semitones_as_fract (1, true);
356 int st = fract_as_semitones (shuttle_fract, rev);
357 shuttle_fract = semitones_as_fract (st - 1, rev);
360 shuttle_fract -= 0.00125;
369 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
370 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
372 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
373 to the far side of it.
376 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
377 switch (ev->direction) {
379 case GDK_SCROLL_RIGHT:
380 shuttle_fract = upper_side_of_dead_zone;
382 case GDK_SCROLL_DOWN:
383 case GDK_SCROLL_LEFT:
384 shuttle_fract = lower_side_of_dead_zone;
387 /* impossible, checked above */
393 use_shuttle_fract (true);
399 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
401 if (!_session || !shuttle_grabbed) {
405 return mouse_shuttle (ev->x, false);
409 ShuttleControl::mouse_shuttle (double x, bool force)
411 double const center = get_width() / 2.0;
412 double distance_from_center = x - center;
414 if (distance_from_center > 0) {
415 distance_from_center = min (distance_from_center, center);
417 distance_from_center = max (distance_from_center, -center);
420 /* compute shuttle fract as expressing how far between the center
421 and the edge we are. positive values indicate we are right of
422 center, negative values indicate left of center
425 shuttle_fract = distance_from_center / center; // center == half the width
426 use_shuttle_fract (force);
431 ShuttleControl::set_shuttle_fract (double f, bool zero_ok)
434 use_shuttle_fract (false, zero_ok);
438 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
440 assert (speed != 0.0);
444 return (int) round (12.0 * fast_log2 (-speed));
447 return (int) round (12.0 * fast_log2 (speed));
452 ShuttleControl::semitones_as_speed (int semi, bool reverse)
455 return -pow (2.0, (semi / 12.0));
457 return pow (2.0, (semi / 12.0));
462 ShuttleControl::semitones_as_fract (int semi, bool reverse)
464 float speed = semitones_as_speed (semi, reverse);
465 return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
469 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
471 assert (fract != 0.0);
472 return speed_as_semitones (fract * 4.0, reverse);
476 ShuttleControl::use_shuttle_fract (bool force, bool zero_ok)
478 microseconds_t now = get_microseconds();
480 shuttle_fract = max (-1.0f, shuttle_fract);
481 shuttle_fract = min (1.0f, shuttle_fract);
483 /* do not attempt to submit a motion-driven transport speed request
484 more than once per process cycle.
487 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
491 last_shuttle_request = now;
495 if (Config->get_shuttle_units() == Semitones) {
496 if (shuttle_fract != 0.0) {
498 int semi = fract_as_semitones (shuttle_fract, reverse);
499 speed = semitones_as_speed (semi, reverse);
504 speed = shuttle_max_speed * shuttle_fract;
508 _session->request_transport_speed (speed, Config->get_shuttle_behaviour() == Wheel);
510 _session->request_transport_speed_nonzero (speed, Config->get_shuttle_behaviour() == Wheel);
515 ShuttleControl::render (cairo_t* cr)
517 cairo_text_extents_t extents;
520 cairo_set_source_rgb (cr, 0, 0.0, 0.0);
521 rounded_rectangle (cr, 0, 0, get_width(), get_height(), 4);
522 // cairo_fill_preserve (cr);
523 // cairo_stroke (cr);
529 speed = _session->transport_speed ();
533 float visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
534 float marker_size = get_height()-4;
535 float avail_width = get_width() - marker_size;
536 float x = get_width()*0.5 + visual_fraction * avail_width*0.5;
537 float offset = x - marker_size*0.5;
538 // cairo_set_source_rgb (cr, 0, 1, 0.0);
539 cairo_set_source (cr, pattern);
541 cairo_move_to( cr, offset-4, 2);
542 cairo_line_to( cr, offset+4, 2+marker_size*0.5);
543 cairo_line_to( cr, offset-4, 2+marker_size);
544 cairo_line_to( cr, offset-4, 2);
545 } else if ( speed ==0.0 )
546 rounded_rectangle (cr, offset, 4, marker_size-2, marker_size-2, 1);
548 cairo_arc (cr, offset + marker_size*0.5, 2 + marker_size*0.5, marker_size*0.5, 0, 360);
549 cairo_set_line_width (cr, 2);
558 if (Config->get_shuttle_units() == Percentage) {
561 snprintf (buf, sizeof (buf), "%s", _("Playing"));
564 snprintf (buf, sizeof (buf), "<<< %d%%", (int) round (-speed * 100));
566 snprintf (buf, sizeof (buf), ">>> %d%%", (int) round (speed * 100));
573 int semi = speed_as_semitones (speed, reversed);
576 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
578 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
583 snprintf (buf, sizeof (buf), "%s", _("Stopped"));
586 last_speed_displayed = speed;
588 cairo_set_source_rgb (cr, 0.6, 0.6, 0.6);
589 cairo_text_extents (cr, buf, &extents);
590 cairo_move_to (cr, 10, extents.height + 4);
591 cairo_set_font_size (cr, 13.0);
592 cairo_show_text (cr, buf);
597 switch (Config->get_shuttle_behaviour()) {
599 snprintf (buf, sizeof (buf), "%s", _("Sprung"));
602 snprintf (buf, sizeof (buf), "%s", _("Wheel"));
606 cairo_text_extents (cr, buf, &extents);
607 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 4);
608 cairo_show_text (cr, buf);
610 float _corner_radius = 4.0;
613 float rheight = 10.0;
614 Gtkmm2ext::rounded_rectangle (cr, 2, 1, get_width()-4, rheight, _corner_radius);
615 cairo_set_source (cr, shine_pattern);
618 if (ARDOUR::Config->get_widget_prelight()) {
620 rounded_rectangle (cr, 1, 1, get_width()-2, get_height()-2, _corner_radius);
621 cairo_set_source_rgba (cr, 1, 1, 1, 0.2);
628 ShuttleControl::shuttle_unit_clicked ()
630 if (shuttle_unit_menu == 0) {
631 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
633 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
637 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
639 Config->set_shuttle_behaviour (s);
643 ShuttleControl::set_shuttle_units (ShuttleUnits s)
645 Config->set_shuttle_units (s);
649 ShuttleControl::update_speed_display ()
651 if (_session->transport_speed() != last_speed_displayed) {
656 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
657 : PBD::Controllable (X_("Shuttle"))
663 ShuttleControl::ShuttleControllable::set_value (double val)
665 sc.set_shuttle_fract ((val - lower()) / (upper() - lower()), true);
669 ShuttleControl::ShuttleControllable::get_value () const
671 return lower() + (sc.get_shuttle_fract () * (upper() - lower()));
675 ShuttleControl::parameter_changed (std::string p)
677 if (p == "shuttle-behaviour") {
678 switch (Config->get_shuttle_behaviour ()) {
680 /* back to Sprung - reset to speed = 1.0 if playing
683 if (_session->transport_rolling()) {
684 if (_session->transport_speed() == 1.0) {
687 /* reset current speed and
688 revert to 1.0 as the default
690 _session->request_transport_speed (1.0);
691 /* redraw when speed changes */
704 } else if (p == "shuttle-units") {
711 ShuttleControl::on_enter_notify_event (GdkEventCrossing* ev)
715 if (ARDOUR::Config->get_widget_prelight()) {
719 return CairoWidget::on_enter_notify_event (ev);
723 ShuttleControl::on_leave_notify_event (GdkEventCrossing* ev)
727 if (ARDOUR::Config->get_widget_prelight()) {
731 return CairoWidget::on_leave_notify_event (ev);