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