Delete trailing whitespace
[ardour.git] / gtk2_ardour / shuttle_control.cc
1 /*
2     Copyright (C) 2011 Paul Davis
3
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.
8
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.
13
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.
17 */
18
19 #include <algorithm>
20
21 #include <cairo/cairo.h>
22
23 #include "ardour/ardour.h"
24 #include "ardour/audioengine.h"
25 #include "ardour/rc_configuration.h"
26 #include "ardour/session.h"
27
28 #include "gtkmm2ext/keyboard.h"
29 #include "gtkmm2ext/gui_thread.h"
30
31 #include "ardour_ui.h"
32 #include "rgb_macros.h"
33 #include "shuttle_control.h"
34
35 #include "i18n.h"
36
37 using namespace Gtk;
38 using namespace Gtkmm2ext;
39 using namespace ARDOUR;
40 using std::min;
41 using std::max;
42
43 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
44 {
45         return FALSE;
46 }
47
48 ShuttleControl::ShuttleControl ()
49         : _controllable (new ShuttleControllable (*this))
50         , binding_proxy (_controllable)
51 {
52         ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
53
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;
64
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"));
69
70         Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
71
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));
75 }
76
77 ShuttleControl::~ShuttleControl ()
78 {
79         cairo_pattern_destroy (pattern);
80 }
81
82 void
83 ShuttleControl::set_session (Session *s)
84 {
85         SessionHandlePtr::set_session (s);
86
87         if (_session) {
88                 set_sensitive (true);
89                 _session->add_controllable (_controllable);
90         } else {
91                 set_sensitive (false);
92         }
93 }
94
95 void
96 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
97 {
98         if (pattern) {
99                 cairo_pattern_destroy (pattern);
100                 pattern = 0;
101         }
102
103         pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
104
105         /* add 3 color stops */
106
107         uint32_t col = ARDOUR_UI::config()->canvasvar_Shuttle.get();
108
109         int r,b,g,a;
110         UINT_TO_RGBA(col, &r, &g, &b, &a);
111
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);
115
116         DrawingArea::on_size_allocate (alloc);
117 }
118
119 void
120 ShuttleControl::map_transport_state ()
121 {
122         float speed = _session->transport_speed ();
123
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;
133                 }
134         }
135
136         queue_draw ();
137 }
138
139 void
140 ShuttleControl::build_shuttle_context_menu ()
141 {
142         using namespace Menu_Helpers;
143
144         shuttle_context_menu = new Menu();
145         MenuList& items = shuttle_context_menu->items();
146
147         Menu* speed_menu = manage (new Menu());
148         MenuList& speed_items = speed_menu->items();
149
150         Menu* units_menu = manage (new Menu);
151         MenuList& units_items = units_menu->items();
152         RadioMenuItem::Group units_group;
153
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();
157         }
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();
161         }
162         items.push_back (MenuElem (_("Units"), *units_menu));
163
164         Menu* style_menu = manage (new Menu);
165         MenuList& style_items = style_menu->items();
166         RadioMenuItem::Group style_group;
167
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();
171         }
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();
175         }
176
177         items.push_back (MenuElem (_("Mode"), *style_menu));
178
179         RadioMenuItem::Group speed_group;
180
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 ();
184         }
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 ();
188         }
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 ();
192         }
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 ();
196         }
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 ();
200         }
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 ();
204         }
205
206         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
207
208 }
209
210 void
211 ShuttleControl::show_shuttle_context_menu ()
212 {
213         if (shuttle_context_menu == 0) {
214                 build_shuttle_context_menu ();
215         }
216
217         shuttle_context_menu->popup (1, gtk_get_current_event_time());
218 }
219
220 void
221 ShuttleControl::set_shuttle_max_speed (float speed)
222 {
223         shuttle_max_speed = speed;
224 }
225
226 bool
227 ShuttleControl::on_button_press_event (GdkEventButton* ev)
228 {
229         if (!_session) {
230                 return true;
231         }
232
233         if (binding_proxy.button_press_handler (ev)) {
234                 return true;
235         }
236
237         if (Keyboard::is_context_menu_event (ev)) {
238                 show_shuttle_context_menu ();
239                 return true;
240         }
241
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;
249
250         case 2:
251         case 3:
252                 return true;
253                 break;
254         }
255
256         return true;
257 }
258
259 bool
260 ShuttleControl::on_button_release_event (GdkEventButton* ev)
261 {
262         if (!_session) {
263                 return true;
264         }
265
266         switch (ev->button) {
267         case 1:
268                 shuttle_grabbed = false;
269                 remove_modal_grab ();
270
271                 if (Config->get_shuttle_behaviour() == Sprung) {
272                         _session->request_transport_speed (shuttle_speed_on_grab);
273                 } else {
274                         mouse_shuttle (ev->x, true);
275                 }
276
277                 return true;
278
279         case 2:
280                 if (_session->transport_rolling()) {
281                         _session->request_transport_speed (1.0);
282                 }
283                 return true;
284
285         case 3:
286         default:
287                 return true;
288
289         }
290
291         return true;
292 }
293
294 bool
295 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
296 {
297         return false;
298 }
299
300 bool
301 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
302 {
303         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
304                 return true;
305         }
306
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;
318         }
319
320         if (Config->get_shuttle_units() == Semitones) {
321
322                 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
323                 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
324
325                 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
326                    to the far side of it.
327                 */
328
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;
342                         }
343                 }
344         }
345
346         use_shuttle_fract (true);
347
348         return true;
349 }
350
351 bool
352 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
353 {
354         if (!_session || !shuttle_grabbed) {
355                 return true;
356         }
357
358         return mouse_shuttle (ev->x, false);
359 }
360
361 gint
362 ShuttleControl::mouse_shuttle (double x, bool force)
363 {
364         double const center = get_width() / 2.0;
365         double distance_from_center = x - center;
366
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);
371         }
372
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
376         */
377
378         shuttle_fract = distance_from_center / center; // center == half the width
379         use_shuttle_fract (force);
380         return true;
381 }
382
383 void
384 ShuttleControl::set_shuttle_fract (double f)
385 {
386         shuttle_fract = f;
387         use_shuttle_fract (false);
388 }
389
390 int
391 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
392 {
393         assert (speed != 0.0);
394
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));
401         }
402 }
403
404 float
405 ShuttleControl::semitones_as_speed (int semi, bool reverse)
406 {
407         if (reverse) {
408                 return -pow (2.0, (semi / 12.0));
409         } else {
410                 return pow (2.0, (semi / 12.0));
411         }
412 }
413
414 float
415 ShuttleControl::semitones_as_fract (int semi, bool reverse)
416 {
417         float speed = semitones_as_speed (semi, reverse);
418         return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
419 }
420
421 int
422 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
423 {
424         assert (fract != 0.0);
425         return speed_as_semitones (fract * 4.0, reverse);
426 }
427
428 void
429 ShuttleControl::use_shuttle_fract (bool force)
430 {
431         microseconds_t now = get_microseconds();
432
433         shuttle_fract = max (-1.0f, shuttle_fract);
434         shuttle_fract = min (1.0f, shuttle_fract);
435
436         /* do not attempt to submit a motion-driven transport speed request
437            more than once per process cycle.
438         */
439
440         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
441                 return;
442         }
443
444         last_shuttle_request = now;
445
446         double speed = 0;
447
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;
455                 }
456         } else {
457                 speed = shuttle_max_speed * shuttle_fract;
458         }
459
460         _session->request_transport_speed_nonzero (speed);
461 }
462
463 bool
464 ShuttleControl::on_expose_event (GdkEventExpose* event)
465 {
466         cairo_text_extents_t extents;
467         Glib::RefPtr<Gdk::Window> win (get_window());
468         Glib::RefPtr<Gtk::Style> style (get_style());
469
470         cairo_t* cr = gdk_cairo_create (win->gobj());
471
472         cairo_set_source (cr, pattern);
473         cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
474         cairo_fill_preserve (cr);
475
476         cairo_set_source_rgb (cr, 0, 0, 0.0);
477         cairo_stroke (cr);
478
479         float speed = 0.0;
480
481         if (_session) {
482                 speed = _session->transport_speed ();
483         }
484
485         /* Marker */
486
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);
493
494         /* speed text */
495
496         char buf[32];
497
498         if (speed != 0) {
499
500                 if (Config->get_shuttle_units() == Percentage) {
501
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));
509                                 }
510                         }
511
512                 } else {
513
514                         bool reversed;
515                         int semi = speed_as_semitones (speed, reversed);
516
517                         if (reversed) {
518                                 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
519                         } else {
520                                 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
521                         }
522                 }
523
524         } else {
525                 snprintf (buf, sizeof (buf), _("Stopped"));
526         }
527
528         last_speed_displayed = speed;
529
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);
534
535         /* style text */
536
537
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;
545         }
546
547         cairo_text_extents (cr, buf, &extents);
548
549         cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
550         cairo_show_text (cr, buf);
551
552         cairo_destroy (cr);
553
554         return true;
555 }
556
557 void
558 ShuttleControl::shuttle_unit_clicked ()
559 {
560         if (shuttle_unit_menu == 0) {
561                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
562         }
563         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
564 }
565
566 void
567 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
568 {
569         Config->set_shuttle_behaviour (s);
570 }
571
572 void
573 ShuttleControl::set_shuttle_units (ShuttleUnits s)
574 {
575         Config->set_shuttle_units (s);
576 }
577
578 void
579 ShuttleControl::update_speed_display ()
580 {
581         if (_session->transport_speed() != last_speed_displayed) {
582                 queue_draw ();
583         }
584 }
585
586 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
587         : PBD::Controllable (X_("Shuttle"))
588         , sc (s)
589 {
590 }
591
592 void
593 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
594 {
595         _id = str;
596 }
597
598 void
599 ShuttleControl::ShuttleControllable::set_value (double val)
600 {
601         double fract;
602
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);
610                 }
611         }
612
613         sc.set_shuttle_fract (fract);
614 }
615
616 double
617 ShuttleControl::ShuttleControllable::get_value () const
618 {
619         return sc.get_shuttle_fract ();
620 }
621
622 void
623 ShuttleControl::parameter_changed (std::string p)
624 {
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
629                          */
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 */
637                                         }
638                                 } else {
639                                         queue_draw ();
640                                 }
641                         }
642                         break;
643
644                 case Wheel:
645                         queue_draw ();
646                         break;
647                 }
648
649         } else if (p == "shuttle-units") {
650                 queue_draw ();
651         }
652 }