remove unused fade_source member of AudioDiskstream
[ardour2.git] / gtk2_ardour / shuttle_control.cc
blob1274dce6f85c992a584ac007403a1c88f2fde0e6
1 /*
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.
19 #include <algorithm>
21 #include <cairo/cairo.h>
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"
31 #include "ardour_ui.h"
32 #include "shuttle_control.h"
34 #include "i18n.h"
36 using namespace Gtk;
37 using namespace Gtkmm2ext;
38 using namespace ARDOUR;
39 using std::min;
40 using std::max;
42 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
44 return FALSE;
47 ShuttleControl::ShuttleControl ()
48 : _controllable (new ShuttleControllable (*this))
49 , binding_proxy (_controllable)
51 ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
53 pattern = 0;
54 last_shuttle_request = 0;
55 last_speed_displayed = -99999999;
56 shuttle_grabbed = false;
57 shuttle_fract = 0.0;
58 shuttle_max_speed = 8.0f;
59 shuttle_style_menu = 0;
60 shuttle_unit_menu = 0;
61 shuttle_context_menu = 0;
63 set_flags (CAN_FOCUS);
64 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);
65 set_size_request (100, 15);
66 set_name (X_("ShuttleControl"));
68 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
70 /* gtkmm 2.4: the C++ wrapper doesn't work */
71 g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
72 // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
75 ShuttleControl::~ShuttleControl ()
77 cairo_pattern_destroy (pattern);
80 void
81 ShuttleControl::set_session (Session *s)
83 SessionHandlePtr::set_session (s);
85 if (_session) {
86 set_sensitive (true);
87 _session->add_controllable (_controllable);
88 } else {
89 set_sensitive (false);
93 void
94 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
96 if (pattern) {
97 cairo_pattern_destroy (pattern);
98 pattern = 0;
101 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
103 /* add 3 color stops */
105 cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
106 cairo_pattern_add_color_stop_rgb (pattern, 0.5, 0.0, 0.0, 1.0);
107 cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
109 DrawingArea::on_size_allocate (alloc);
112 void
113 ShuttleControl::map_transport_state ()
115 float speed = _session->transport_speed ();
117 if (fabs(speed) <= (2*DBL_EPSILON)) {
118 shuttle_fract = 0;
119 } else {
120 if (Config->get_shuttle_units() == Semitones) {
121 bool reverse;
122 int semi = speed_as_semitones (speed, reverse);
123 shuttle_fract = semitones_as_fract (semi, reverse);
124 } else {
125 shuttle_fract = speed/shuttle_max_speed;
129 queue_draw ();
132 void
133 ShuttleControl::build_shuttle_context_menu ()
135 using namespace Menu_Helpers;
137 shuttle_context_menu = new Menu();
138 MenuList& items = shuttle_context_menu->items();
140 Menu* speed_menu = manage (new Menu());
141 MenuList& speed_items = speed_menu->items();
143 Menu* units_menu = manage (new Menu);
144 MenuList& units_items = units_menu->items();
145 RadioMenuItem::Group units_group;
147 units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
148 if (Config->get_shuttle_units() == Percentage) {
149 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
151 units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
152 if (Config->get_shuttle_units() == Semitones) {
153 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
155 items.push_back (MenuElem (_("Units"), *units_menu));
157 Menu* style_menu = manage (new Menu);
158 MenuList& style_items = style_menu->items();
159 RadioMenuItem::Group style_group;
161 style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
162 if (Config->get_shuttle_behaviour() == Sprung) {
163 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
165 style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
166 if (Config->get_shuttle_behaviour() == Wheel) {
167 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
170 items.push_back (MenuElem (_("Mode"), *style_menu));
172 RadioMenuItem::Group speed_group;
174 speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
175 if (shuttle_max_speed == 8.0) {
176 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
178 speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
179 if (shuttle_max_speed == 6.0) {
180 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
182 speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
183 if (shuttle_max_speed == 4.0) {
184 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
186 speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
187 if (shuttle_max_speed == 3.0) {
188 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
190 speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
191 if (shuttle_max_speed == 2.0) {
192 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
194 speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
195 if (shuttle_max_speed == 1.5) {
196 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
199 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
203 void
204 ShuttleControl::show_shuttle_context_menu ()
206 if (shuttle_context_menu == 0) {
207 build_shuttle_context_menu ();
210 shuttle_context_menu->popup (1, gtk_get_current_event_time());
213 void
214 ShuttleControl::set_shuttle_max_speed (float speed)
216 shuttle_max_speed = speed;
219 bool
220 ShuttleControl::on_button_press_event (GdkEventButton* ev)
222 if (!_session) {
223 return true;
226 if (binding_proxy.button_press_handler (ev)) {
227 return true;
230 if (Keyboard::is_context_menu_event (ev)) {
231 show_shuttle_context_menu ();
232 return true;
235 switch (ev->button) {
236 case 1:
237 add_modal_grab ();
238 shuttle_grabbed = true;
239 mouse_shuttle (ev->x, true);
240 break;
242 case 2:
243 case 3:
244 return true;
245 break;
248 return true;
251 bool
252 ShuttleControl::on_button_release_event (GdkEventButton* ev)
254 if (!_session) {
255 return true;
258 switch (ev->button) {
259 case 1:
260 shuttle_grabbed = false;
261 remove_modal_grab ();
263 if (Config->get_shuttle_behaviour() == Sprung) {
264 if (_session->config.get_auto_play()) {
265 _session->request_transport_speed (1.0);
266 } else {
267 _session->request_transport_speed (0.0);
269 } else {
270 mouse_shuttle (ev->x, true);
273 return true;
275 case 2:
276 if (_session->transport_rolling()) {
277 _session->request_transport_speed (1.0);
279 return true;
281 case 3:
282 default:
283 return true;
287 return true;
290 bool
291 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
293 return false;
296 bool
297 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
299 if (!_session || Config->get_shuttle_behaviour() != Wheel) {
300 return true;
303 switch (ev->direction) {
304 case GDK_SCROLL_UP:
305 case GDK_SCROLL_RIGHT:
306 shuttle_fract += 0.005;
307 break;
308 case GDK_SCROLL_DOWN:
309 case GDK_SCROLL_LEFT:
310 shuttle_fract -= 0.005;
311 break;
312 default:
313 return false;
316 if (Config->get_shuttle_units() == Semitones) {
318 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
319 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
321 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
322 to the far side of it.
325 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
326 switch (ev->direction) {
327 case GDK_SCROLL_UP:
328 case GDK_SCROLL_RIGHT:
329 shuttle_fract = upper_side_of_dead_zone;
330 break;
331 case GDK_SCROLL_DOWN:
332 case GDK_SCROLL_LEFT:
333 shuttle_fract = lower_side_of_dead_zone;
334 break;
335 default:
336 /* impossible, checked above */
337 return false;
342 use_shuttle_fract (true);
344 return true;
347 bool
348 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
350 if (!_session || !shuttle_grabbed) {
351 return true;
354 return mouse_shuttle (ev->x, false);
357 gint
358 ShuttleControl::mouse_shuttle (double x, bool force)
360 double const center = get_width() / 2.0;
361 double distance_from_center = x - center;
363 if (distance_from_center > 0) {
364 distance_from_center = min (distance_from_center, center);
365 } else {
366 distance_from_center = max (distance_from_center, -center);
369 /* compute shuttle fract as expressing how far between the center
370 and the edge we are. positive values indicate we are right of
371 center, negative values indicate left of center
374 shuttle_fract = distance_from_center / center; // center == half the width
375 use_shuttle_fract (force);
376 return true;
379 void
380 ShuttleControl::set_shuttle_fract (double f)
382 shuttle_fract = f;
383 use_shuttle_fract (false);
387 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
389 assert (speed != 0.0);
391 if (speed < 0.0) {
392 reverse = true;
393 return (int) round (12.0 * fast_log2 (-speed));
394 } else {
395 reverse = false;
396 return (int) round (12.0 * fast_log2 (speed));
400 float
401 ShuttleControl::semitones_as_speed (int semi, bool reverse)
403 if (reverse) {
404 return -pow (2.0, (semi / 12.0));
405 } else {
406 return pow (2.0, (semi / 12.0));
410 float
411 ShuttleControl::semitones_as_fract (int semi, bool reverse)
413 float speed = semitones_as_speed (semi, reverse);
414 return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
418 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
420 assert (fract != 0.0);
421 return speed_as_semitones (fract * 4.0, reverse);
424 void
425 ShuttleControl::use_shuttle_fract (bool force)
427 microseconds_t now = get_microseconds();
429 shuttle_fract = max (-1.0f, shuttle_fract);
430 shuttle_fract = min (1.0f, shuttle_fract);
432 /* do not attempt to submit a motion-driven transport speed request
433 more than once per process cycle.
436 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
437 return;
440 last_shuttle_request = now;
442 double speed = 0;
444 if (Config->get_shuttle_units() == Semitones) {
445 if (shuttle_fract != 0.0) {
446 bool reverse;
447 int semi = fract_as_semitones (shuttle_fract, reverse);
448 speed = semitones_as_speed (semi, reverse);
449 } else {
450 speed = 0.0;
452 } else {
453 speed = shuttle_max_speed * shuttle_fract;
456 _session->request_transport_speed_nonzero (speed);
459 bool
460 ShuttleControl::on_expose_event (GdkEventExpose* event)
462 cairo_text_extents_t extents;
463 Glib::RefPtr<Gdk::Window> win (get_window());
464 Glib::RefPtr<Gtk::Style> style (get_style());
466 cairo_t* cr = gdk_cairo_create (win->gobj());
468 cairo_set_source (cr, pattern);
469 cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
470 cairo_fill_preserve (cr);
472 cairo_set_source_rgb (cr, 0, 0, 0.0);
473 cairo_stroke (cr);
475 float speed = 0.0;
477 if (_session) {
478 speed = _session->transport_speed ();
481 /* Marker */
483 double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
484 double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
485 cairo_move_to (cr, x, 1);
486 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
487 cairo_line_to (cr, x, get_height()-1);
488 cairo_stroke (cr);
490 /* speed text */
492 char buf[32];
494 if (speed != 0) {
496 if (Config->get_shuttle_units() == Percentage) {
498 if (speed == 1.0) {
499 snprintf (buf, sizeof (buf), _("Playing"));
500 } else {
501 if (speed < 0.0) {
502 snprintf (buf, sizeof (buf), "<<< %d%%", (int) round (-speed * 100));
503 } else {
504 snprintf (buf, sizeof (buf), ">>> %d%%", (int) round (speed * 100));
508 } else {
510 bool reversed;
511 int semi = speed_as_semitones (speed, reversed);
513 if (reversed) {
514 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
515 } else {
516 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
520 } else {
521 snprintf (buf, sizeof (buf), _("Stopped"));
524 last_speed_displayed = speed;
526 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
527 cairo_text_extents (cr, buf, &extents);
528 cairo_move_to (cr, 10, extents.height + 2);
529 cairo_show_text (cr, buf);
531 /* style text */
534 switch (Config->get_shuttle_behaviour()) {
535 case Sprung:
536 snprintf (buf, sizeof (buf), _("Sprung"));
537 break;
538 case Wheel:
539 snprintf (buf, sizeof (buf), _("Wheel"));
540 break;
543 cairo_text_extents (cr, buf, &extents);
545 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
546 cairo_show_text (cr, buf);
548 cairo_destroy (cr);
550 return true;
553 void
554 ShuttleControl::shuttle_unit_clicked ()
556 if (shuttle_unit_menu == 0) {
557 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
559 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
562 void
563 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
565 Config->set_shuttle_behaviour (s);
568 void
569 ShuttleControl::set_shuttle_units (ShuttleUnits s)
571 Config->set_shuttle_units (s);
574 void
575 ShuttleControl::update_speed_display ()
577 if (_session->transport_speed() != last_speed_displayed) {
578 queue_draw ();
582 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
583 : PBD::Controllable (X_("Shuttle"))
584 , sc (s)
588 void
589 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
591 _id = str;
594 void
595 ShuttleControl::ShuttleControllable::set_value (double val)
597 double fract;
599 if (val == 0.5) {
600 fract = 0.0;
601 } else {
602 if (val < 0.5) {
603 fract = -((0.5 - val)/0.5);
604 } else {
605 fract = ((val - 0.5)/0.5);
609 sc.set_shuttle_fract (fract);
612 double
613 ShuttleControl::ShuttleControllable::get_value () const
615 return sc.get_shuttle_fract ();
618 void
619 ShuttleControl::parameter_changed (std::string p)
621 if (p == "shuttle-behaviour") {
622 switch (Config->get_shuttle_behaviour ()) {
623 case Sprung:
624 /* back to Sprung - reset to speed = 1.0 if playing
626 if (_session) {
627 if (_session->transport_rolling()) {
628 if (_session->transport_speed() == 1.0) {
629 queue_draw ();
630 } else {
631 _session->request_transport_speed (1.0);
632 /* redraw when speed changes */
634 } else {
635 queue_draw ();
638 break;
640 case Wheel:
641 queue_draw ();
642 break;
645 } else if (p == "shuttle-units") {
646 queue_draw ();