hopefully fix up shuttle operation in semitones mode
[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 (speed != 0.0) {
116                 if (Config->get_shuttle_units() == Semitones) {
117                         shuttle_fract = semitones_as_fract (speed_as_semitones (speed));
118                 } else {
119                         shuttle_fract = speed/shuttle_max_speed;
120                 }
121         } else {
122                 shuttle_fract = 0;
123         }
124
125         queue_draw ();
126 }
127
128 void
129 ShuttleControl::build_shuttle_context_menu ()
130 {
131         using namespace Menu_Helpers;
132
133         shuttle_context_menu = new Menu();
134         MenuList& items = shuttle_context_menu->items();
135
136         Menu* speed_menu = manage (new Menu());
137         MenuList& speed_items = speed_menu->items();
138
139         Menu* units_menu = manage (new Menu);
140         MenuList& units_items = units_menu->items();
141         RadioMenuItem::Group units_group;
142         
143         units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
144         if (Config->get_shuttle_units() == Percentage) {
145                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
146         }
147         units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
148         if (Config->get_shuttle_units() == Semitones) {
149                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
150         }
151         items.push_back (MenuElem (_("Units"), *units_menu));
152         
153         Menu* style_menu = manage (new Menu);
154         MenuList& style_items = style_menu->items();
155         RadioMenuItem::Group style_group;
156
157         style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
158         if (Config->get_shuttle_behaviour() == Sprung) {
159                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
160         }
161         style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
162         if (Config->get_shuttle_behaviour() == Wheel) {
163                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
164         }
165         
166         items.push_back (MenuElem (_("Mode"), *style_menu));
167
168         RadioMenuItem::Group speed_group;
169
170         speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
171         if (shuttle_max_speed == 8.0) {
172                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
173         }
174         speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
175         if (shuttle_max_speed == 6.0) {
176                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
177         }
178         speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
179         if (shuttle_max_speed == 4.0) {
180                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
181         }
182         speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
183         if (shuttle_max_speed == 3.0) {
184                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
185         }
186         speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
187         if (shuttle_max_speed == 2.0) {
188                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
189         }
190         speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
191         if (shuttle_max_speed == 1.5) {
192                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
193         }
194
195         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
196         
197 }
198
199 void
200 ShuttleControl::show_shuttle_context_menu ()
201 {
202         if (shuttle_context_menu == 0) {
203                 build_shuttle_context_menu ();
204         }
205
206         shuttle_context_menu->popup (1, gtk_get_current_event_time());
207 }
208
209 void
210 ShuttleControl::set_shuttle_max_speed (float speed)
211 {
212         shuttle_max_speed = speed;
213 }
214
215 bool
216 ShuttleControl::on_button_press_event (GdkEventButton* ev)
217 {
218         if (!_session) {
219                 return true;
220         }
221
222         if (binding_proxy.button_press_handler (ev)) {
223                 return true;
224         }
225
226         if (Keyboard::is_context_menu_event (ev)) {
227                 show_shuttle_context_menu ();
228                 return true;
229         }
230
231         switch (ev->button) {
232         case 1:
233                 add_modal_grab ();
234                 shuttle_grabbed = true;
235                 mouse_shuttle (ev->x, true);
236                 break;
237
238         case 2:
239         case 3:
240                 return true;
241                 break;
242         }
243
244         return true;
245 }
246
247 bool
248 ShuttleControl::on_button_release_event (GdkEventButton* ev)
249 {
250         if (!_session) {
251                 return true;
252         }
253
254         switch (ev->button) {
255         case 1:
256                 shuttle_grabbed = false;
257                 remove_modal_grab ();
258
259                 if (Config->get_shuttle_behaviour() == Sprung) {
260                         if (_session->config.get_auto_play()) {
261                                 _session->request_transport_speed (1.0);
262                         } else {
263                                 _session->request_transport_speed (0.0);
264                         }
265                 } else {
266                         mouse_shuttle (ev->x, true);
267                 }
268
269                 return true;
270
271         case 2:
272                 if (_session->transport_rolling()) {
273                         _session->request_transport_speed (1.0);
274                 } 
275                 return true;
276
277         case 3:
278         default:
279                 return true;
280
281         }
282
283         return true;
284 }
285
286 bool
287 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
288 {
289         return false;
290 }
291
292 bool
293 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
294 {
295         if (!_session || Config->get_shuttle_behaviour() != Wheel) {
296                 return true;
297         }
298
299         switch (ev->direction) {
300
301         case GDK_SCROLL_UP:
302         case GDK_SCROLL_RIGHT:
303                 shuttle_fract += 0.005;
304                 break;
305         case GDK_SCROLL_DOWN:
306         case GDK_SCROLL_LEFT:
307                 shuttle_fract -= 0.005;
308                 break;
309         default:
310                 return false;
311         }
312
313         use_shuttle_fract (true);
314
315         return true;
316 }
317
318 bool
319 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
320 {
321         if (!_session || !shuttle_grabbed) {
322                 return true;
323         }
324
325         return mouse_shuttle (ev->x, false);
326 }
327
328 gint
329 ShuttleControl::mouse_shuttle (double x, bool force)
330 {
331         double const center = get_width() / 2.0;
332         double distance_from_center = x - center;
333
334         if (distance_from_center > 0) {
335                 distance_from_center = min (distance_from_center, center);
336         } else {
337                 distance_from_center = max (distance_from_center, -center);
338         }
339
340         /* compute shuttle fract as expressing how far between the center
341            and the edge we are. positive values indicate we are right of 
342            center, negative values indicate left of center
343         */
344
345         shuttle_fract = distance_from_center / center; // center == half the width
346         use_shuttle_fract (force);
347         return true;
348 }
349
350 void
351 ShuttleControl::set_shuttle_fract (double f)
352 {
353         shuttle_fract = f;
354         use_shuttle_fract (false);
355 }
356
357 int
358 ShuttleControl::speed_as_semitones (float speed)
359 {
360         if (speed < 0.0) {
361                 return (int) round (12.0 * fast_log2 (-speed));
362         } else {
363                 return (int) round (12.0 * fast_log2 (speed));
364         }
365 }        
366
367 float
368 ShuttleControl::semitones_as_speed (int semi)
369 {
370         return pow (2.0, (semi / 12.0));
371 }
372
373 float
374 ShuttleControl::semitones_as_fract (int semi)
375 {
376         double const step = 1.0 / 24.0; // range is 24 semitones up & down
377         return semi * step;
378 }
379
380 int
381 ShuttleControl::fract_as_semitones (float fract)
382 {
383         double const step = 1.0 / 24.0; // range is 24 semitones up & down
384         return (int) round (shuttle_fract / step);
385 }
386
387 void
388 ShuttleControl::use_shuttle_fract (bool force)
389 {
390         microseconds_t now = get_microseconds();
391
392         if (Config->get_shuttle_units() == Semitones) {
393                 shuttle_fract = max (-1.0f, shuttle_fract);
394                 shuttle_fract = min (1.0f, shuttle_fract);
395         } else {
396                 shuttle_fract = max (-shuttle_max_speed, shuttle_fract);
397                 shuttle_fract = min (shuttle_max_speed, shuttle_fract);
398         }
399
400         /* do not attempt to submit a motion-driven transport speed request
401            more than once per process cycle.
402          */
403
404         if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
405                 return;
406         }
407
408         last_shuttle_request = now;
409
410         double speed = 0;
411
412         if (Config->get_shuttle_units() == Semitones) {
413                 speed = semitones_as_speed (fract_as_semitones (shuttle_fract));
414         } else {
415                 speed = shuttle_max_speed * shuttle_fract;
416         }
417
418         _session->request_transport_speed_nonzero (speed);
419 }
420
421 bool
422 ShuttleControl::on_expose_event (GdkEventExpose* event)
423 {
424         cairo_text_extents_t extents;
425         Glib::RefPtr<Gdk::Window> win (get_window());
426         Glib::RefPtr<Gtk::Style> style (get_style());
427
428         cairo_t* cr = gdk_cairo_create (win->gobj());   
429
430         cairo_set_source (cr, pattern);
431         cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
432         cairo_fill_preserve (cr);
433
434         cairo_set_source_rgb (cr, 0, 0, 0.0);
435         cairo_stroke (cr);
436
437         float speed = 0.0;
438
439         if (_session) {
440                 speed = _session->transport_speed ();
441         }
442
443         /* Marker */
444
445         double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
446         double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
447         cairo_move_to (cr, x, 0);
448         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
449         cairo_line_to (cr, x, get_height());
450         cairo_stroke (cr);
451
452         /* speed text */
453
454         char buf[32];
455         if (speed != 0) {
456                 if (Config->get_shuttle_units() == Percentage) {
457                         if (speed == 1.0) {
458                                 snprintf (buf, sizeof (buf), _("Playing"));
459                         } else {
460                                 snprintf (buf, sizeof (buf), "%d%%", (int) round (speed * 100));
461                         }
462                 } else {
463                         snprintf (buf, sizeof (buf), "%+d semitones", speed_as_semitones (speed));
464                 }
465         } else {
466                 snprintf (buf, sizeof (buf), _("Stopped"));
467         }
468
469         last_speed_displayed = speed;
470
471         cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
472         cairo_text_extents (cr, buf, &extents);
473         cairo_move_to (cr, 10, extents.height + 2);
474         cairo_show_text (cr, buf);
475
476         /* style text */
477
478
479         switch (Config->get_shuttle_behaviour()) {
480         case Sprung:
481                 snprintf (buf, sizeof (buf), _("Sprung"));
482                 break;
483         case Wheel:
484                 snprintf (buf, sizeof (buf), _("Wheel"));
485                 break;
486         }
487
488         cairo_text_extents (cr, buf, &extents);
489
490         cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
491         cairo_show_text (cr, buf);
492
493         cairo_destroy (cr);
494
495         return true;
496 }
497
498 void
499 ShuttleControl::shuttle_unit_clicked ()
500 {
501         if (shuttle_unit_menu == 0) {
502                 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
503         }
504         shuttle_unit_menu->popup (1, gtk_get_current_event_time());
505 }
506
507 void
508 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
509 {
510         Config->set_shuttle_behaviour (s);
511 }
512
513 void
514 ShuttleControl::set_shuttle_units (ShuttleUnits s)
515 {
516         Config->set_shuttle_units (s);
517 }
518
519 void
520 ShuttleControl::update_speed_display ()
521 {
522         if (_session->transport_speed() != last_speed_displayed) {
523                 queue_draw ();
524         }
525 }
526       
527 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
528         : PBD::Controllable (X_("Shuttle")) 
529         , sc (s)
530 {
531 }
532
533 void
534 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
535 {
536         _id = str;
537 }
538
539 void
540 ShuttleControl::ShuttleControllable::set_value (double val)
541 {
542         double fract;
543         
544         if (val == 0.5) {
545                 fract = 0.0;
546         } else {
547                 if (val < 0.5) {
548                         fract = -((0.5 - val)/0.5);
549                 } else {
550                         fract = ((val - 0.5)/0.5);
551                 }
552         }
553
554         sc.set_shuttle_fract (fract);
555 }
556
557 double 
558 ShuttleControl::ShuttleControllable::get_value () const
559 {
560         return sc.get_shuttle_fract ();
561 }
562
563 void
564 ShuttleControl::parameter_changed (std::string p)
565 {
566         if (p == "shuttle-behaviour") {
567                 switch (Config->get_shuttle_behaviour ()) {
568                 case Sprung:
569                         /* back to Sprung - reset to speed = 1.0 if playing
570                          */
571                         if (_session) {
572                                 if (_session->transport_rolling()) {
573                                         if (_session->transport_speed() == 1.0) {
574                                                 queue_draw ();
575                                         } else {
576                                                 _session->request_transport_speed (1.0);
577                                                 /* redraw when speed changes */
578                                         }
579                                 } else {
580                                         queue_draw ();
581                                 }
582                         }
583                         break;
584
585                 case Wheel:
586                         queue_draw ();
587                         break;
588                 }
589                         
590         } else if (p == "shuttle-units") {
591                 queue_draw ();
592         }
593 }