aa45c04643deeef3dc14548a63f7d599e4df84f5
[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 <pbd/stacktrace.h>
24
25 #include "ardour/ardour.h"
26 #include "ardour/audioengine.h"
27 #include "ardour/rc_configuration.h"
28 #include "ardour/session.h"
29
30 #include "gtkmm2ext/keyboard.h"
31 #include "gtkmm2ext/gui_thread.h"
32
33 #include "ardour_ui.h"
34 #include "shuttle_control.h"
35
36 using namespace Gtk;
37 using namespace Gtkmm2ext;
38 using namespace ARDOUR;
39 using std::min;
40 using std::max;
41
42 ShuttleControl::ShuttleControl ()
43         : _controllable (new ShuttleControllable (*this))
44         , binding_proxy (_controllable)
45 {
46         ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
47
48         pattern = 0;
49         last_shuttle_request = 0;
50         last_speed_displayed = -99999999;
51         shuttle_grabbed = false;
52         shuttle_fract = 0.0;
53         shuttle_max_speed = 8.0f;
54         shuttle_style_menu = 0;
55         shuttle_unit_menu = 0;
56         shuttle_context_menu = 0;
57
58         set_flags (CAN_FOCUS);
59         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);
60         set_size_request (100, 15);
61         set_name (X_("ShuttleControl"));
62
63         Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
64
65         signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
66 }
67
68 ShuttleControl::~ShuttleControl ()
69 {
70         cairo_pattern_destroy (pattern);
71 }
72
73 void
74 ShuttleControl::set_session (Session *s)
75 {
76         SessionHandlePtr::set_session (s);
77
78         if (_session) {
79                 set_sensitive (true);
80                 _session->add_controllable (_controllable);
81         } else {
82                 set_sensitive (false);
83         }
84 }
85
86 void
87 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
88 {
89         if (pattern) {
90                 cairo_pattern_destroy (pattern);
91                 pattern = 0;
92         }
93
94         pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
95         
96         /* add 3 color stops */
97
98         cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
99         cairo_pattern_add_color_stop_rgb (pattern, 0.5, 0.0, 0.0, 1.0);
100         cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
101
102         DrawingArea::on_size_allocate (alloc);
103 }
104
105 void
106 ShuttleControl::map_transport_state ()
107 {
108         float speed = _session->transport_speed ();
109
110         if (speed != 0.0) {
111                 if (Config->get_shuttle_units() == Semitones) {
112                         shuttle_fract = speed/2.0;
113                 } else {
114                         shuttle_fract = speed/shuttle_max_speed;
115                 }
116         } else {
117                 shuttle_fract = 0;
118         }
119
120         queue_draw ();
121 }
122
123 void
124 ShuttleControl::build_shuttle_context_menu ()
125 {
126         using namespace Menu_Helpers;
127
128         shuttle_context_menu = new Menu();
129         MenuList& items = shuttle_context_menu->items();
130
131         Menu* speed_menu = manage (new Menu());
132         MenuList& speed_items = speed_menu->items();
133
134         Menu* units_menu = manage (new Menu);
135         MenuList& units_items = units_menu->items();
136         RadioMenuItem::Group units_group;
137         
138         units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
139         if (Config->get_shuttle_units() == Percentage) {
140                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
141         }
142         units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
143         if (Config->get_shuttle_units() == Semitones) {
144                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
145         }
146         items.push_back (MenuElem (_("Units"), *units_menu));
147         
148         Menu* style_menu = manage (new Menu);
149         MenuList& style_items = style_menu->items();
150         RadioMenuItem::Group style_group;
151
152         style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
153         if (Config->get_shuttle_behaviour() == Sprung) {
154                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
155         }
156         style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
157         if (Config->get_shuttle_behaviour() == Wheel) {
158                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
159         }
160         
161         items.push_back (MenuElem (_("Mode"), *style_menu));
162
163         RadioMenuItem::Group speed_group;
164
165         speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
166         if (shuttle_max_speed == 8.0) {
167                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
168         }
169         speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
170         if (shuttle_max_speed == 6.0) {
171                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
172         }
173         speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
174         if (shuttle_max_speed == 4.0) {
175                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
176         }
177         speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
178         if (shuttle_max_speed == 3.0) {
179                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
180         }
181         speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
182         if (shuttle_max_speed == 2.0) {
183                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
184         }
185         speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
186         if (shuttle_max_speed == 1.5) {
187                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
188         }
189
190         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
191         
192 }
193
194 void
195 ShuttleControl::show_shuttle_context_menu ()
196 {
197         if (shuttle_context_menu == 0) {
198                 build_shuttle_context_menu ();
199         }
200
201         shuttle_context_menu->popup (1, gtk_get_current_event_time());
202 }
203
204 void
205 ShuttleControl::set_shuttle_max_speed (float speed)
206 {
207         shuttle_max_speed = speed;
208 }
209
210 bool
211 ShuttleControl::on_button_press_event (GdkEventButton* ev)
212 {
213         if (!_session) {
214                 return true;
215         }
216
217         if (binding_proxy.button_press_handler (ev)) {
218                 return true;
219         }
220
221         if (Keyboard::is_context_menu_event (ev)) {
222                 show_shuttle_context_menu ();
223                 return true;
224         }
225
226         switch (ev->button) {
227         case 1:
228                 add_modal_grab ();
229                 shuttle_grabbed = true;
230                 mouse_shuttle (ev->x, true);
231                 break;
232
233         case 2:
234         case 3:
235                 return true;
236                 break;
237         }
238
239         return true;
240 }
241
242 bool
243 ShuttleControl::on_button_release_event (GdkEventButton* ev)
244 {
245         if (!_session) {
246                 return true;
247         }
248
249         switch (ev->button) {
250         case 1:
251                 mouse_shuttle (ev->x, true);
252                 shuttle_grabbed = false;
253                 remove_modal_grab ();
254                 if (Config->get_shuttle_behaviour() == Sprung) {
255                         if (_session->config.get_auto_play()) {
256                                 shuttle_fract = SHUTTLE_FRACT_SPEED1;
257                                 _session->request_transport_speed (1.0);
258                         } else {
259                                 shuttle_fract = 0;
260                                 _session->request_transport_speed (0.0);
261                         }
262                         queue_draw ();
263                 }
264                 return true;
265
266         case 2:
267                 if (_session->transport_rolling()) {
268                         shuttle_fract = SHUTTLE_FRACT_SPEED1;
269                         _session->request_transport_speed (1.0);
270                 } else {
271                         shuttle_fract = 0;
272                 }
273                 queue_draw ();
274                 return true;
275
276         case 3:
277         default:
278                 return true;
279
280         }
281
282         use_shuttle_fract (true);
283
284         return true;
285 }
286
287 bool
288 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
289 {
290         std::cerr << "OQT!\n";
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
303         case GDK_SCROLL_UP:
304         case GDK_SCROLL_RIGHT:
305                 shuttle_fract += 0.005;
306                 break;
307         case GDK_SCROLL_DOWN:
308         case GDK_SCROLL_LEFT:
309                 shuttle_fract -= 0.005;
310                 break;
311         default:
312                 return false;
313         }
314
315         use_shuttle_fract (true);
316
317         return true;
318 }
319
320 bool
321 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
322 {
323         if (!_session || !shuttle_grabbed) {
324                 return true;
325         }
326
327         return mouse_shuttle (ev->x, false);
328 }
329
330 gint
331 ShuttleControl::mouse_shuttle (double x, bool force)
332 {
333         double const half_width = get_width() / 2.0;
334         double distance = x - half_width;
335
336         if (distance > 0) {
337                 distance = min (distance, half_width);
338         } else {
339                 distance = max (distance, -half_width);
340         }
341
342         shuttle_fract = distance / half_width;
343         use_shuttle_fract (force);
344         return true;
345 }
346
347 void
348 ShuttleControl::set_shuttle_fract (double f)
349 {
350         shuttle_fract = f;
351         use_shuttle_fract (false);
352 }
353
354 void
355 ShuttleControl::use_shuttle_fract (bool force)
356 {
357         microseconds_t now = get_microseconds();
358
359         /* do not attempt to submit a motion-driven transport speed request
360            more than once per process cycle.
361          */
362
363         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
364                 return;
365         }
366
367         last_shuttle_request = now;
368
369         double speed = 0;
370
371         if (Config->get_shuttle_units() == Semitones) {
372                 double const step = 1.0 / 24.0; // range is 24 semitones up & down
373                 double const semitones = round (shuttle_fract / step);
374                 speed = pow (2.0, (semitones / 12.0));
375         } else {
376                 speed = shuttle_max_speed * shuttle_fract;
377         }
378
379         _session->request_transport_speed_nonzero (speed);
380 }
381
382 bool
383 ShuttleControl::on_expose_event (GdkEventExpose* event)
384 {
385         cairo_text_extents_t extents;
386         Glib::RefPtr<Gdk::Window> win (get_window());
387         Glib::RefPtr<Gtk::Style> style (get_style());
388
389         cairo_t* cr = gdk_cairo_create (win->gobj());   
390
391         cairo_set_source (cr, pattern);
392         cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
393         cairo_fill_preserve (cr);
394
395         cairo_set_source_rgb (cr, 0, 0, 0.0);
396         cairo_stroke (cr);
397
398         float speed = 0.0;
399
400         if (_session) {
401                 speed = _session->transport_speed ();
402         }
403
404         /* Marker */
405
406         double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
407         double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
408         cairo_move_to (cr, x, 0);
409         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
410         cairo_line_to (cr, x, get_height());
411         cairo_stroke (cr);
412
413         /* speed text */
414
415         char buf[32];
416         if (speed != 0) {
417                 if (Config->get_shuttle_units() == Percentage) {
418                         if (speed == 1.0) {
419                                 snprintf (buf, sizeof (buf), _("Playing"));
420                         } else {
421                                 snprintf (buf, sizeof (buf), "%d%%", (int) round (speed * 100));
422                         }
423                 } else {
424                         if (speed < 0) {
425                                 snprintf (buf, sizeof (buf), "-%d semitones", (int) round (12.0 * fast_log2 (-speed)));
426                         } else {
427                                 snprintf (buf, sizeof (buf), "+%d semitones", (int) round (12.0 * fast_log2 (speed)));
428                         }
429                 }
430         } else {
431                 snprintf (buf, sizeof (buf), _("Stopped"));
432         }
433
434         last_speed_displayed = speed;
435
436         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
437         cairo_text_extents (cr, buf, &extents);
438         cairo_move_to (cr, 10, extents.height + 2);
439         cairo_show_text (cr, buf);
440
441         /* style text */
442
443
444         switch (Config->get_shuttle_behaviour()) {
445         case Sprung:
446                 snprintf (buf, sizeof (buf), _("Sprung"));
447                 break;
448         case Wheel:
449                 snprintf (buf, sizeof (buf), _("Wheel"));
450                 break;
451         }
452
453         cairo_text_extents (cr, buf, &extents);
454
455         cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
456         cairo_show_text (cr, buf);
457
458         cairo_destroy (cr);
459
460         return true;
461 }
462
463 void
464 ShuttleControl::shuttle_unit_clicked ()
465 {
466         if (shuttle_unit_menu == 0) {
467                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
468         }
469         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
470 }
471
472 void
473 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
474 {
475         Config->set_shuttle_behaviour (s);
476 }
477
478 void
479 ShuttleControl::set_shuttle_units (ShuttleUnits s)
480 {
481         Config->set_shuttle_units (s);
482 }
483
484 void
485 ShuttleControl::update_speed_display ()
486 {
487         if (_session->transport_speed() != last_speed_displayed) {
488                 queue_draw ();
489         }
490 }
491       
492 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
493         : PBD::Controllable (X_("Shuttle")) 
494         , sc (s)
495 {
496 }
497
498 void
499 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
500 {
501         _id = str;
502 }
503
504 void
505 ShuttleControl::ShuttleControllable::set_value (double val)
506 {
507         double fract;
508         
509         if (val == 0.5) {
510                 fract = 0.0;
511         } else {
512                 if (val < 0.5) {
513                         fract = -((0.5 - val)/0.5);
514                 } else {
515                         fract = ((val - 0.5)/0.5);
516                 }
517         }
518
519         sc.set_shuttle_fract (fract);
520 }
521
522 double 
523 ShuttleControl::ShuttleControllable::get_value () const
524 {
525         return sc.get_shuttle_fract ();
526 }
527
528 void
529 ShuttleControl::parameter_changed (std::string p)
530 {
531         if (p == "shuttle-behaviour") {
532                 switch (Config->get_shuttle_behaviour ()) {
533                 case Sprung:
534                         /* back to Sprung - reset to speed = 1.0 if playing
535                          */
536                         if (_session) {
537                                 if (_session->transport_rolling()) {
538                                         if (_session->transport_speed() == 1.0) {
539                                                 queue_draw ();
540                                         } else {
541                                                 _session->request_transport_speed (1.0);
542                                                 /* redraw when speed changes */
543                                         }
544                                 } else {
545                                         queue_draw ();
546                                 }
547                         }
548                         break;
549
550                 case Wheel:
551                         queue_draw ();
552                         break;
553                 }
554                         
555         } else if (p == "shuttle-units") {
556                 queue_draw ();
557         }
558 }