Remove erroneous assert which I added earlier.
[ardour2.git] / gtk2_ardour / shuttle_control.cc
blob24f70904981d905ad96c3e2d6b70a6f4e7735c1f
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 "rgb_macros.h"
33 #include "shuttle_control.h"
35 #include "i18n.h"
37 using namespace Gtk;
38 using namespace Gtkmm2ext;
39 using namespace ARDOUR;
40 using std::min;
41 using std::max;
43 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
45 return FALSE;
48 ShuttleControl::ShuttleControl ()
49 : _controllable (new ShuttleControllable (*this))
50 , binding_proxy (_controllable)
52 ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
54 pattern = 0;
55 last_shuttle_request = 0;
56 last_speed_displayed = -99999999;
57 shuttle_grabbed = false;
58 shuttle_speed_on_grab = 0;
59 shuttle_fract = 0.0;
60 shuttle_max_speed = 8.0f;
61 shuttle_style_menu = 0;
62 shuttle_unit_menu = 0;
63 shuttle_context_menu = 0;
65 set_flags (CAN_FOCUS);
66 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);
67 set_size_request (100, 15);
68 set_name (X_("ShuttleControl"));
70 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
72 /* gtkmm 2.4: the C++ wrapper doesn't work */
73 g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
74 // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
77 ShuttleControl::~ShuttleControl ()
79 cairo_pattern_destroy (pattern);
82 void
83 ShuttleControl::set_session (Session *s)
85 SessionHandlePtr::set_session (s);
87 if (_session) {
88 set_sensitive (true);
89 _session->add_controllable (_controllable);
90 } else {
91 set_sensitive (false);
95 void
96 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
98 if (pattern) {
99 cairo_pattern_destroy (pattern);
100 pattern = 0;
103 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
105 /* add 3 color stops */
107 uint32_t col = ARDOUR_UI::config()->canvasvar_Shuttle.get();
109 int r,b,g,a;
110 UINT_TO_RGBA(col, &r, &g, &b, &a);
112 cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
113 cairo_pattern_add_color_stop_rgb (pattern, 0.5, r/255.0, g/255.0, b/255.0);
114 cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
116 DrawingArea::on_size_allocate (alloc);
119 void
120 ShuttleControl::map_transport_state ()
122 float speed = _session->transport_speed ();
124 if (fabs(speed) <= (2*DBL_EPSILON)) {
125 shuttle_fract = 0;
126 } else {
127 if (Config->get_shuttle_units() == Semitones) {
128 bool reverse;
129 int semi = speed_as_semitones (speed, reverse);
130 shuttle_fract = semitones_as_fract (semi, reverse);
131 } else {
132 shuttle_fract = speed/shuttle_max_speed;
136 queue_draw ();
139 void
140 ShuttleControl::build_shuttle_context_menu ()
142 using namespace Menu_Helpers;
144 shuttle_context_menu = new Menu();
145 MenuList& items = shuttle_context_menu->items();
147 Menu* speed_menu = manage (new Menu());
148 MenuList& speed_items = speed_menu->items();
150 Menu* units_menu = manage (new Menu);
151 MenuList& units_items = units_menu->items();
152 RadioMenuItem::Group units_group;
154 units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
155 if (Config->get_shuttle_units() == Percentage) {
156 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
158 units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
159 if (Config->get_shuttle_units() == Semitones) {
160 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
162 items.push_back (MenuElem (_("Units"), *units_menu));
164 Menu* style_menu = manage (new Menu);
165 MenuList& style_items = style_menu->items();
166 RadioMenuItem::Group style_group;
168 style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
169 if (Config->get_shuttle_behaviour() == Sprung) {
170 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
172 style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
173 if (Config->get_shuttle_behaviour() == Wheel) {
174 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
177 items.push_back (MenuElem (_("Mode"), *style_menu));
179 RadioMenuItem::Group speed_group;
181 speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
182 if (shuttle_max_speed == 8.0) {
183 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
185 speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
186 if (shuttle_max_speed == 6.0) {
187 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
189 speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
190 if (shuttle_max_speed == 4.0) {
191 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
193 speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
194 if (shuttle_max_speed == 3.0) {
195 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
197 speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
198 if (shuttle_max_speed == 2.0) {
199 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
201 speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
202 if (shuttle_max_speed == 1.5) {
203 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
206 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
210 void
211 ShuttleControl::show_shuttle_context_menu ()
213 if (shuttle_context_menu == 0) {
214 build_shuttle_context_menu ();
217 shuttle_context_menu->popup (1, gtk_get_current_event_time());
220 void
221 ShuttleControl::set_shuttle_max_speed (float speed)
223 shuttle_max_speed = speed;
226 bool
227 ShuttleControl::on_button_press_event (GdkEventButton* ev)
229 if (!_session) {
230 return true;
233 if (binding_proxy.button_press_handler (ev)) {
234 return true;
237 if (Keyboard::is_context_menu_event (ev)) {
238 show_shuttle_context_menu ();
239 return true;
242 switch (ev->button) {
243 case 1:
244 add_modal_grab ();
245 shuttle_grabbed = true;
246 shuttle_speed_on_grab = _session->transport_speed ();
247 mouse_shuttle (ev->x, true);
248 break;
250 case 2:
251 case 3:
252 return true;
253 break;
256 return true;
259 bool
260 ShuttleControl::on_button_release_event (GdkEventButton* ev)
262 if (!_session) {
263 return true;
266 switch (ev->button) {
267 case 1:
268 shuttle_grabbed = false;
269 remove_modal_grab ();
271 if (Config->get_shuttle_behaviour() == Sprung) {
272 _session->request_transport_speed (shuttle_speed_on_grab);
273 } else {
274 mouse_shuttle (ev->x, true);
277 return true;
279 case 2:
280 if (_session->transport_rolling()) {
281 _session->request_transport_speed (1.0);
283 return true;
285 case 3:
286 default:
287 return true;
291 return true;
294 bool
295 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
297 return false;
300 bool
301 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
303 if (!_session || Config->get_shuttle_behaviour() != Wheel) {
304 return true;
307 switch (ev->direction) {
308 case GDK_SCROLL_UP:
309 case GDK_SCROLL_RIGHT:
310 shuttle_fract += 0.005;
311 break;
312 case GDK_SCROLL_DOWN:
313 case GDK_SCROLL_LEFT:
314 shuttle_fract -= 0.005;
315 break;
316 default:
317 return false;
320 if (Config->get_shuttle_units() == Semitones) {
322 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
323 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
325 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
326 to the far side of it.
329 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
330 switch (ev->direction) {
331 case GDK_SCROLL_UP:
332 case GDK_SCROLL_RIGHT:
333 shuttle_fract = upper_side_of_dead_zone;
334 break;
335 case GDK_SCROLL_DOWN:
336 case GDK_SCROLL_LEFT:
337 shuttle_fract = lower_side_of_dead_zone;
338 break;
339 default:
340 /* impossible, checked above */
341 return false;
346 use_shuttle_fract (true);
348 return true;
351 bool
352 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
354 if (!_session || !shuttle_grabbed) {
355 return true;
358 return mouse_shuttle (ev->x, false);
361 gint
362 ShuttleControl::mouse_shuttle (double x, bool force)
364 double const center = get_width() / 2.0;
365 double distance_from_center = x - center;
367 if (distance_from_center > 0) {
368 distance_from_center = min (distance_from_center, center);
369 } else {
370 distance_from_center = max (distance_from_center, -center);
373 /* compute shuttle fract as expressing how far between the center
374 and the edge we are. positive values indicate we are right of
375 center, negative values indicate left of center
378 shuttle_fract = distance_from_center / center; // center == half the width
379 use_shuttle_fract (force);
380 return true;
383 void
384 ShuttleControl::set_shuttle_fract (double f)
386 shuttle_fract = f;
387 use_shuttle_fract (false);
391 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
393 assert (speed != 0.0);
395 if (speed < 0.0) {
396 reverse = true;
397 return (int) round (12.0 * fast_log2 (-speed));
398 } else {
399 reverse = false;
400 return (int) round (12.0 * fast_log2 (speed));
404 float
405 ShuttleControl::semitones_as_speed (int semi, bool reverse)
407 if (reverse) {
408 return -pow (2.0, (semi / 12.0));
409 } else {
410 return pow (2.0, (semi / 12.0));
414 float
415 ShuttleControl::semitones_as_fract (int semi, bool reverse)
417 float speed = semitones_as_speed (semi, reverse);
418 return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
422 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
424 assert (fract != 0.0);
425 return speed_as_semitones (fract * 4.0, reverse);
428 void
429 ShuttleControl::use_shuttle_fract (bool force)
431 microseconds_t now = get_microseconds();
433 shuttle_fract = max (-1.0f, shuttle_fract);
434 shuttle_fract = min (1.0f, shuttle_fract);
436 /* do not attempt to submit a motion-driven transport speed request
437 more than once per process cycle.
440 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
441 return;
444 last_shuttle_request = now;
446 double speed = 0;
448 if (Config->get_shuttle_units() == Semitones) {
449 if (shuttle_fract != 0.0) {
450 bool reverse;
451 int semi = fract_as_semitones (shuttle_fract, reverse);
452 speed = semitones_as_speed (semi, reverse);
453 } else {
454 speed = 0.0;
456 } else {
457 speed = shuttle_max_speed * shuttle_fract;
460 _session->request_transport_speed_nonzero (speed);
463 bool
464 ShuttleControl::on_expose_event (GdkEventExpose* event)
466 cairo_text_extents_t extents;
467 Glib::RefPtr<Gdk::Window> win (get_window());
468 Glib::RefPtr<Gtk::Style> style (get_style());
470 cairo_t* cr = gdk_cairo_create (win->gobj());
472 cairo_set_source (cr, pattern);
473 cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
474 cairo_fill_preserve (cr);
476 cairo_set_source_rgb (cr, 0, 0, 0.0);
477 cairo_stroke (cr);
479 float speed = 0.0;
481 if (_session) {
482 speed = _session->transport_speed ();
485 /* Marker */
487 double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
488 double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
489 cairo_move_to (cr, x, 1);
490 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
491 cairo_line_to (cr, x, get_height()-1);
492 cairo_stroke (cr);
494 /* speed text */
496 char buf[32];
498 if (speed != 0) {
500 if (Config->get_shuttle_units() == Percentage) {
502 if (speed == 1.0) {
503 snprintf (buf, sizeof (buf), _("Playing"));
504 } else {
505 if (speed < 0.0) {
506 snprintf (buf, sizeof (buf), "<<< %d%%", (int) round (-speed * 100));
507 } else {
508 snprintf (buf, sizeof (buf), ">>> %d%%", (int) round (speed * 100));
512 } else {
514 bool reversed;
515 int semi = speed_as_semitones (speed, reversed);
517 if (reversed) {
518 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
519 } else {
520 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
524 } else {
525 snprintf (buf, sizeof (buf), _("Stopped"));
528 last_speed_displayed = speed;
530 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
531 cairo_text_extents (cr, buf, &extents);
532 cairo_move_to (cr, 10, extents.height + 2);
533 cairo_show_text (cr, buf);
535 /* style text */
538 switch (Config->get_shuttle_behaviour()) {
539 case Sprung:
540 snprintf (buf, sizeof (buf), _("Sprung"));
541 break;
542 case Wheel:
543 snprintf (buf, sizeof (buf), _("Wheel"));
544 break;
547 cairo_text_extents (cr, buf, &extents);
549 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
550 cairo_show_text (cr, buf);
552 cairo_destroy (cr);
554 return true;
557 void
558 ShuttleControl::shuttle_unit_clicked ()
560 if (shuttle_unit_menu == 0) {
561 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
563 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
566 void
567 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
569 Config->set_shuttle_behaviour (s);
572 void
573 ShuttleControl::set_shuttle_units (ShuttleUnits s)
575 Config->set_shuttle_units (s);
578 void
579 ShuttleControl::update_speed_display ()
581 if (_session->transport_speed() != last_speed_displayed) {
582 queue_draw ();
586 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
587 : PBD::Controllable (X_("Shuttle"))
588 , sc (s)
592 void
593 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
595 _id = str;
598 void
599 ShuttleControl::ShuttleControllable::set_value (double val)
601 double fract;
603 if (val == 0.5) {
604 fract = 0.0;
605 } else {
606 if (val < 0.5) {
607 fract = -((0.5 - val)/0.5);
608 } else {
609 fract = ((val - 0.5)/0.5);
613 sc.set_shuttle_fract (fract);
616 double
617 ShuttleControl::ShuttleControllable::get_value () const
619 return sc.get_shuttle_fract ();
622 void
623 ShuttleControl::parameter_changed (std::string p)
625 if (p == "shuttle-behaviour") {
626 switch (Config->get_shuttle_behaviour ()) {
627 case Sprung:
628 /* back to Sprung - reset to speed = 1.0 if playing
630 if (_session) {
631 if (_session->transport_rolling()) {
632 if (_session->transport_speed() == 1.0) {
633 queue_draw ();
634 } else {
635 _session->request_transport_speed (1.0);
636 /* redraw when speed changes */
638 } else {
639 queue_draw ();
642 break;
644 case Wheel:
645 queue_draw ();
646 break;
649 } else if (p == "shuttle-units") {
650 queue_draw ();