Copy local state in AudioRegionView copy constructor. Fixes #4047.
[ardour2.git] / gtk2_ardour / shuttle_control.cc
blob5193e4cb6e060d252b0397ef4bc24f7c5b7d89f6
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_speed_on_grab = 0;
58 shuttle_fract = 0.0;
59 shuttle_max_speed = 8.0f;
60 shuttle_style_menu = 0;
61 shuttle_unit_menu = 0;
62 shuttle_context_menu = 0;
64 set_flags (CAN_FOCUS);
65 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);
66 set_size_request (100, 15);
67 set_name (X_("ShuttleControl"));
69 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
71 /* gtkmm 2.4: the C++ wrapper doesn't work */
72 g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
73 // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
76 ShuttleControl::~ShuttleControl ()
78 cairo_pattern_destroy (pattern);
81 void
82 ShuttleControl::set_session (Session *s)
84 SessionHandlePtr::set_session (s);
86 if (_session) {
87 set_sensitive (true);
88 _session->add_controllable (_controllable);
89 } else {
90 set_sensitive (false);
94 void
95 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
97 if (pattern) {
98 cairo_pattern_destroy (pattern);
99 pattern = 0;
102 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
104 /* add 3 color stops */
106 cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
107 cairo_pattern_add_color_stop_rgb (pattern, 0.5, 0.0, 0.0, 1.0);
108 cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
110 DrawingArea::on_size_allocate (alloc);
113 void
114 ShuttleControl::map_transport_state ()
116 float speed = _session->transport_speed ();
118 if (fabs(speed) <= (2*DBL_EPSILON)) {
119 shuttle_fract = 0;
120 } else {
121 if (Config->get_shuttle_units() == Semitones) {
122 bool reverse;
123 int semi = speed_as_semitones (speed, reverse);
124 shuttle_fract = semitones_as_fract (semi, reverse);
125 } else {
126 shuttle_fract = speed/shuttle_max_speed;
130 queue_draw ();
133 void
134 ShuttleControl::build_shuttle_context_menu ()
136 using namespace Menu_Helpers;
138 shuttle_context_menu = new Menu();
139 MenuList& items = shuttle_context_menu->items();
141 Menu* speed_menu = manage (new Menu());
142 MenuList& speed_items = speed_menu->items();
144 Menu* units_menu = manage (new Menu);
145 MenuList& units_items = units_menu->items();
146 RadioMenuItem::Group units_group;
148 units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
149 if (Config->get_shuttle_units() == Percentage) {
150 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
152 units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
153 if (Config->get_shuttle_units() == Semitones) {
154 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
156 items.push_back (MenuElem (_("Units"), *units_menu));
158 Menu* style_menu = manage (new Menu);
159 MenuList& style_items = style_menu->items();
160 RadioMenuItem::Group style_group;
162 style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
163 if (Config->get_shuttle_behaviour() == Sprung) {
164 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
166 style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
167 if (Config->get_shuttle_behaviour() == Wheel) {
168 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
171 items.push_back (MenuElem (_("Mode"), *style_menu));
173 RadioMenuItem::Group speed_group;
175 speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
176 if (shuttle_max_speed == 8.0) {
177 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
179 speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
180 if (shuttle_max_speed == 6.0) {
181 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
183 speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
184 if (shuttle_max_speed == 4.0) {
185 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
187 speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
188 if (shuttle_max_speed == 3.0) {
189 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
191 speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
192 if (shuttle_max_speed == 2.0) {
193 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
195 speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
196 if (shuttle_max_speed == 1.5) {
197 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
200 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
204 void
205 ShuttleControl::show_shuttle_context_menu ()
207 if (shuttle_context_menu == 0) {
208 build_shuttle_context_menu ();
211 shuttle_context_menu->popup (1, gtk_get_current_event_time());
214 void
215 ShuttleControl::set_shuttle_max_speed (float speed)
217 shuttle_max_speed = speed;
220 bool
221 ShuttleControl::on_button_press_event (GdkEventButton* ev)
223 if (!_session) {
224 return true;
227 if (binding_proxy.button_press_handler (ev)) {
228 return true;
231 if (Keyboard::is_context_menu_event (ev)) {
232 show_shuttle_context_menu ();
233 return true;
236 switch (ev->button) {
237 case 1:
238 add_modal_grab ();
239 shuttle_grabbed = true;
240 shuttle_speed_on_grab = _session->transport_speed ();
241 mouse_shuttle (ev->x, true);
242 break;
244 case 2:
245 case 3:
246 return true;
247 break;
250 return true;
253 bool
254 ShuttleControl::on_button_release_event (GdkEventButton* ev)
256 if (!_session) {
257 return true;
260 switch (ev->button) {
261 case 1:
262 shuttle_grabbed = false;
263 remove_modal_grab ();
265 if (Config->get_shuttle_behaviour() == Sprung) {
266 _session->request_transport_speed (shuttle_speed_on_grab);
267 } else {
268 mouse_shuttle (ev->x, true);
271 return true;
273 case 2:
274 if (_session->transport_rolling()) {
275 _session->request_transport_speed (1.0);
277 return true;
279 case 3:
280 default:
281 return true;
285 return true;
288 bool
289 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
291 return false;
294 bool
295 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
297 if (!_session || Config->get_shuttle_behaviour() != Wheel) {
298 return true;
301 switch (ev->direction) {
302 case GDK_SCROLL_UP:
303 case GDK_SCROLL_RIGHT:
304 shuttle_fract += 0.005;
305 break;
306 case GDK_SCROLL_DOWN:
307 case GDK_SCROLL_LEFT:
308 shuttle_fract -= 0.005;
309 break;
310 default:
311 return false;
314 if (Config->get_shuttle_units() == Semitones) {
316 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
317 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
319 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
320 to the far side of it.
323 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
324 switch (ev->direction) {
325 case GDK_SCROLL_UP:
326 case GDK_SCROLL_RIGHT:
327 shuttle_fract = upper_side_of_dead_zone;
328 break;
329 case GDK_SCROLL_DOWN:
330 case GDK_SCROLL_LEFT:
331 shuttle_fract = lower_side_of_dead_zone;
332 break;
333 default:
334 /* impossible, checked above */
335 return false;
340 use_shuttle_fract (true);
342 return true;
345 bool
346 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
348 if (!_session || !shuttle_grabbed) {
349 return true;
352 return mouse_shuttle (ev->x, false);
355 gint
356 ShuttleControl::mouse_shuttle (double x, bool force)
358 double const center = get_width() / 2.0;
359 double distance_from_center = x - center;
361 if (distance_from_center > 0) {
362 distance_from_center = min (distance_from_center, center);
363 } else {
364 distance_from_center = max (distance_from_center, -center);
367 /* compute shuttle fract as expressing how far between the center
368 and the edge we are. positive values indicate we are right of
369 center, negative values indicate left of center
372 shuttle_fract = distance_from_center / center; // center == half the width
373 use_shuttle_fract (force);
374 return true;
377 void
378 ShuttleControl::set_shuttle_fract (double f)
380 shuttle_fract = f;
381 use_shuttle_fract (false);
385 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
387 assert (speed != 0.0);
389 if (speed < 0.0) {
390 reverse = true;
391 return (int) round (12.0 * fast_log2 (-speed));
392 } else {
393 reverse = false;
394 return (int) round (12.0 * fast_log2 (speed));
398 float
399 ShuttleControl::semitones_as_speed (int semi, bool reverse)
401 if (reverse) {
402 return -pow (2.0, (semi / 12.0));
403 } else {
404 return pow (2.0, (semi / 12.0));
408 float
409 ShuttleControl::semitones_as_fract (int semi, bool reverse)
411 float speed = semitones_as_speed (semi, reverse);
412 return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
416 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
418 assert (fract != 0.0);
419 return speed_as_semitones (fract * 4.0, reverse);
422 void
423 ShuttleControl::use_shuttle_fract (bool force)
425 microseconds_t now = get_microseconds();
427 shuttle_fract = max (-1.0f, shuttle_fract);
428 shuttle_fract = min (1.0f, shuttle_fract);
430 /* do not attempt to submit a motion-driven transport speed request
431 more than once per process cycle.
434 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
435 return;
438 last_shuttle_request = now;
440 double speed = 0;
442 if (Config->get_shuttle_units() == Semitones) {
443 if (shuttle_fract != 0.0) {
444 bool reverse;
445 int semi = fract_as_semitones (shuttle_fract, reverse);
446 speed = semitones_as_speed (semi, reverse);
447 } else {
448 speed = 0.0;
450 } else {
451 speed = shuttle_max_speed * shuttle_fract;
454 _session->request_transport_speed_nonzero (speed);
457 bool
458 ShuttleControl::on_expose_event (GdkEventExpose* event)
460 cairo_text_extents_t extents;
461 Glib::RefPtr<Gdk::Window> win (get_window());
462 Glib::RefPtr<Gtk::Style> style (get_style());
464 cairo_t* cr = gdk_cairo_create (win->gobj());
466 cairo_set_source (cr, pattern);
467 cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
468 cairo_fill_preserve (cr);
470 cairo_set_source_rgb (cr, 0, 0, 0.0);
471 cairo_stroke (cr);
473 float speed = 0.0;
475 if (_session) {
476 speed = _session->transport_speed ();
479 /* Marker */
481 double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
482 double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
483 cairo_move_to (cr, x, 1);
484 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
485 cairo_line_to (cr, x, get_height()-1);
486 cairo_stroke (cr);
488 /* speed text */
490 char buf[32];
492 if (speed != 0) {
494 if (Config->get_shuttle_units() == Percentage) {
496 if (speed == 1.0) {
497 snprintf (buf, sizeof (buf), _("Playing"));
498 } else {
499 if (speed < 0.0) {
500 snprintf (buf, sizeof (buf), "<<< %d%%", (int) round (-speed * 100));
501 } else {
502 snprintf (buf, sizeof (buf), ">>> %d%%", (int) round (speed * 100));
506 } else {
508 bool reversed;
509 int semi = speed_as_semitones (speed, reversed);
511 if (reversed) {
512 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
513 } else {
514 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
518 } else {
519 snprintf (buf, sizeof (buf), _("Stopped"));
522 last_speed_displayed = speed;
524 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
525 cairo_text_extents (cr, buf, &extents);
526 cairo_move_to (cr, 10, extents.height + 2);
527 cairo_show_text (cr, buf);
529 /* style text */
532 switch (Config->get_shuttle_behaviour()) {
533 case Sprung:
534 snprintf (buf, sizeof (buf), _("Sprung"));
535 break;
536 case Wheel:
537 snprintf (buf, sizeof (buf), _("Wheel"));
538 break;
541 cairo_text_extents (cr, buf, &extents);
543 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
544 cairo_show_text (cr, buf);
546 cairo_destroy (cr);
548 return true;
551 void
552 ShuttleControl::shuttle_unit_clicked ()
554 if (shuttle_unit_menu == 0) {
555 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
557 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
560 void
561 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
563 Config->set_shuttle_behaviour (s);
566 void
567 ShuttleControl::set_shuttle_units (ShuttleUnits s)
569 Config->set_shuttle_units (s);
572 void
573 ShuttleControl::update_speed_display ()
575 if (_session->transport_speed() != last_speed_displayed) {
576 queue_draw ();
580 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
581 : PBD::Controllable (X_("Shuttle"))
582 , sc (s)
586 void
587 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
589 _id = str;
592 void
593 ShuttleControl::ShuttleControllable::set_value (double val)
595 double fract;
597 if (val == 0.5) {
598 fract = 0.0;
599 } else {
600 if (val < 0.5) {
601 fract = -((0.5 - val)/0.5);
602 } else {
603 fract = ((val - 0.5)/0.5);
607 sc.set_shuttle_fract (fract);
610 double
611 ShuttleControl::ShuttleControllable::get_value () const
613 return sc.get_shuttle_fract ();
616 void
617 ShuttleControl::parameter_changed (std::string p)
619 if (p == "shuttle-behaviour") {
620 switch (Config->get_shuttle_behaviour ()) {
621 case Sprung:
622 /* back to Sprung - reset to speed = 1.0 if playing
624 if (_session) {
625 if (_session->transport_rolling()) {
626 if (_session->transport_speed() == 1.0) {
627 queue_draw ();
628 } else {
629 _session->request_transport_speed (1.0);
630 /* redraw when speed changes */
632 } else {
633 queue_draw ();
636 break;
638 case Wheel:
639 queue_draw ();
640 break;
643 } else if (p == "shuttle-units") {
644 queue_draw ();