c941336de51d6829886637450fbc4a1d2e7fab88
[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 "shuttle_control.h"
33
34 using namespace Gtk;
35 using namespace Gtkmm2ext;
36 using namespace ARDOUR;
37 using std::min;
38 using std::max;
39
40 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
41 {
42         return FALSE;
43 }
44
45 ShuttleControl::ShuttleControl ()
46         : _controllable (new ShuttleControllable (*this))
47         , binding_proxy (_controllable)
48 {
49         ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
50
51         pattern = 0;
52         last_shuttle_request = 0;
53         last_speed_displayed = -99999999;
54         shuttle_grabbed = false;
55         shuttle_fract = 0.0;
56         shuttle_max_speed = 8.0f;
57         shuttle_style_menu = 0;
58         shuttle_unit_menu = 0;
59         shuttle_context_menu = 0;
60
61         set_flags (CAN_FOCUS);
62         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);
63         set_size_request (100, 15);
64         set_name (X_("ShuttleControl"));
65
66         Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
67
68         /* gtkmm 2.4: the C++ wrapper doesn't work */
69         g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
70         // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
71 }
72
73 ShuttleControl::~ShuttleControl ()
74 {
75         cairo_pattern_destroy (pattern);
76 }
77
78 void
79 ShuttleControl::set_session (Session *s)
80 {
81         SessionHandlePtr::set_session (s);
82
83         if (_session) {
84                 set_sensitive (true);
85                 _session->add_controllable (_controllable);
86         } else {
87                 set_sensitive (false);
88         }
89 }
90
91 void
92 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
93 {
94         if (pattern) {
95                 cairo_pattern_destroy (pattern);
96                 pattern = 0;
97         }
98
99         pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
100         
101         /* add 3 color stops */
102
103         cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
104         cairo_pattern_add_color_stop_rgb (pattern, 0.5, 0.0, 0.0, 1.0);
105         cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
106
107         DrawingArea::on_size_allocate (alloc);
108 }
109
110 void
111 ShuttleControl::map_transport_state ()
112 {
113         float speed = _session->transport_speed ();
114
115         if (fabs(speed) <= (2*DBL_EPSILON)) {
116                 shuttle_fract = 0;
117         } else {
118                 if (Config->get_shuttle_units() == Semitones) {
119                         bool reverse;
120                         int semi = speed_as_semitones (speed, reverse);
121                         shuttle_fract = semitones_as_fract (semi, reverse);
122                 } else {
123                         shuttle_fract = speed/shuttle_max_speed;
124                 }
125         }
126
127         queue_draw ();
128 }
129
130 void
131 ShuttleControl::build_shuttle_context_menu ()
132 {
133         using namespace Menu_Helpers;
134
135         shuttle_context_menu = new Menu();
136         MenuList& items = shuttle_context_menu->items();
137
138         Menu* speed_menu = manage (new Menu());
139         MenuList& speed_items = speed_menu->items();
140
141         Menu* units_menu = manage (new Menu);
142         MenuList& units_items = units_menu->items();
143         RadioMenuItem::Group units_group;
144         
145         units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
146         if (Config->get_shuttle_units() == Percentage) {
147                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
148         }
149         units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
150         if (Config->get_shuttle_units() == Semitones) {
151                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
152         }
153         items.push_back (MenuElem (_("Units"), *units_menu));
154         
155         Menu* style_menu = manage (new Menu);
156         MenuList& style_items = style_menu->items();
157         RadioMenuItem::Group style_group;
158
159         style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
160         if (Config->get_shuttle_behaviour() == Sprung) {
161                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
162         }
163         style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
164         if (Config->get_shuttle_behaviour() == Wheel) {
165                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
166         }
167         
168         items.push_back (MenuElem (_("Mode"), *style_menu));
169
170         RadioMenuItem::Group speed_group;
171
172         speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
173         if (shuttle_max_speed == 8.0) {
174                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
175         }
176         speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
177         if (shuttle_max_speed == 6.0) {
178                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
179         }
180         speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
181         if (shuttle_max_speed == 4.0) {
182                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
183         }
184         speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
185         if (shuttle_max_speed == 3.0) {
186                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
187         }
188         speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
189         if (shuttle_max_speed == 2.0) {
190                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
191         }
192         speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
193         if (shuttle_max_speed == 1.5) {
194                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
195         }
196
197         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
198         
199 }
200
201 void
202 ShuttleControl::show_shuttle_context_menu ()
203 {
204         if (shuttle_context_menu == 0) {
205                 build_shuttle_context_menu ();
206         }
207
208         shuttle_context_menu->popup (1, gtk_get_current_event_time());
209 }
210
211 void
212 ShuttleControl::set_shuttle_max_speed (float speed)
213 {
214         shuttle_max_speed = speed;
215 }
216
217 bool
218 ShuttleControl::on_button_press_event (GdkEventButton* ev)
219 {
220         if (!_session) {
221                 return true;
222         }
223
224         if (binding_proxy.button_press_handler (ev)) {
225                 return true;
226         }
227
228         if (Keyboard::is_context_menu_event (ev)) {
229                 show_shuttle_context_menu ();
230                 return true;
231         }
232
233         switch (ev->button) {
234         case 1:
235                 add_modal_grab ();
236                 shuttle_grabbed = true;
237                 mouse_shuttle (ev->x, true);
238                 break;
239
240         case 2:
241         case 3:
242                 return true;
243                 break;
244         }
245
246         return true;
247 }
248
249 bool
250 ShuttleControl::on_button_release_event (GdkEventButton* ev)
251 {
252         if (!_session) {
253                 return true;
254         }
255
256         switch (ev->button) {
257         case 1:
258                 shuttle_grabbed = false;
259                 remove_modal_grab ();
260
261                 if (Config->get_shuttle_behaviour() == Sprung) {
262                         if (_session->config.get_auto_play()) {
263                                 _session->request_transport_speed (1.0);
264                         } else {
265                                 _session->request_transport_speed (0.0);
266                         }
267                 } else {
268                         mouse_shuttle (ev->x, true);
269                 }
270
271                 return true;
272
273         case 2:
274                 if (_session->transport_rolling()) {
275                         _session->request_transport_speed (1.0);
276                 } 
277                 return true;
278
279         case 3:
280         default:
281                 return true;
282
283         }
284
285         return true;
286 }
287
288 bool
289 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
290 {
291         return false;
292 }
293
294 bool
295 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
296 {
297         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
298                 return true;
299         }
300
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;
312         }
313         
314         if (Config->get_shuttle_units() == Semitones) {
315
316                 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
317                 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
318
319                 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
320                    to the far side of it.
321                 */
322
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;
336                         }
337                 }
338         }
339
340         use_shuttle_fract (true);
341
342         return true;
343 }
344
345 bool
346 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
347 {
348         if (!_session || !shuttle_grabbed) {
349                 return true;
350         }
351
352         return mouse_shuttle (ev->x, false);
353 }
354
355 gint
356 ShuttleControl::mouse_shuttle (double x, bool force)
357 {
358         double const center = get_width() / 2.0;
359         double distance_from_center = x - center;
360
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);
365         }
366
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
370         */
371
372         shuttle_fract = distance_from_center / center; // center == half the width
373         use_shuttle_fract (force);
374         return true;
375 }
376
377 void
378 ShuttleControl::set_shuttle_fract (double f)
379 {
380         shuttle_fract = f;
381         use_shuttle_fract (false);
382 }
383
384 int
385 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
386 {
387         assert (speed != 0.0);
388
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));
395         }
396 }        
397
398 float
399 ShuttleControl::semitones_as_speed (int semi, bool reverse)
400 {
401         if (reverse) {
402                 return -pow (2.0, (semi / 12.0));
403         } else {
404                 return pow (2.0, (semi / 12.0));
405         }
406 }
407
408 float
409 ShuttleControl::semitones_as_fract (int semi, bool reverse)
410 {
411         float speed = semitones_as_speed (semi, reverse);
412         return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
413 }
414
415 int
416 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
417 {
418         assert (fract != 0.0);
419         return speed_as_semitones (fract * 4.0, reverse);
420 }
421
422 void
423 ShuttleControl::use_shuttle_fract (bool force)
424 {
425         microseconds_t now = get_microseconds();
426
427         shuttle_fract = max (-1.0f, shuttle_fract);
428         shuttle_fract = min (1.0f, shuttle_fract);
429
430         /* do not attempt to submit a motion-driven transport speed request
431            more than once per process cycle.
432          */
433
434         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
435                 return;
436         }
437
438         last_shuttle_request = now;
439
440         double speed = 0;
441
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;
449                 }
450         } else {
451                 speed = shuttle_max_speed * shuttle_fract;
452         }
453
454         _session->request_transport_speed_nonzero (speed);
455 }
456
457 bool
458 ShuttleControl::on_expose_event (GdkEventExpose* event)
459 {
460         cairo_text_extents_t extents;
461         Glib::RefPtr<Gdk::Window> win (get_window());
462         Glib::RefPtr<Gtk::Style> style (get_style());
463
464         cairo_t* cr = gdk_cairo_create (win->gobj());   
465
466         cairo_set_source (cr, pattern);
467         cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
468         cairo_fill_preserve (cr);
469
470         cairo_set_source_rgb (cr, 0, 0, 0.0);
471         cairo_stroke (cr);
472
473         float speed = 0.0;
474
475         if (_session) {
476                 speed = _session->transport_speed ();
477         }
478
479         /* Marker */
480
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);
487
488         /* speed text */
489
490         char buf[32];
491
492         if (speed != 0) {
493
494                 if (Config->get_shuttle_units() == Percentage) {
495
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));
503                                 }
504                         }
505
506                 } else {
507
508                         bool reversed;
509                         int semi = speed_as_semitones (speed, reversed);
510
511                         if (reversed) {
512                                 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
513                         } else {
514                                 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
515                         }
516                 }
517
518         } else {
519                 snprintf (buf, sizeof (buf), _("Stopped"));
520         }
521
522         last_speed_displayed = speed;
523
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);
528
529         /* style text */
530
531
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;
539         }
540
541         cairo_text_extents (cr, buf, &extents);
542
543         cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
544         cairo_show_text (cr, buf);
545
546         cairo_destroy (cr);
547
548         return true;
549 }
550
551 void
552 ShuttleControl::shuttle_unit_clicked ()
553 {
554         if (shuttle_unit_menu == 0) {
555                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
556         }
557         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
558 }
559
560 void
561 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
562 {
563         Config->set_shuttle_behaviour (s);
564 }
565
566 void
567 ShuttleControl::set_shuttle_units (ShuttleUnits s)
568 {
569         Config->set_shuttle_units (s);
570 }
571
572 void
573 ShuttleControl::update_speed_display ()
574 {
575         if (_session->transport_speed() != last_speed_displayed) {
576                 queue_draw ();
577         }
578 }
579       
580 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
581         : PBD::Controllable (X_("Shuttle")) 
582         , sc (s)
583 {
584 }
585
586 void
587 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
588 {
589         _id = str;
590 }
591
592 void
593 ShuttleControl::ShuttleControllable::set_value (double val)
594 {
595         double fract;
596         
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);
604                 }
605         }
606
607         sc.set_shuttle_fract (fract);
608 }
609
610 double 
611 ShuttleControl::ShuttleControllable::get_value () const
612 {
613         return sc.get_shuttle_fract ();
614 }
615
616 void
617 ShuttleControl::parameter_changed (std::string p)
618 {
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
623                          */
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 */
631                                         }
632                                 } else {
633                                         queue_draw ();
634                                 }
635                         }
636                         break;
637
638                 case Wheel:
639                         queue_draw ();
640                         break;
641                 }
642                         
643         } else if (p == "shuttle-units") {
644                 queue_draw ();
645         }
646 }