9182cc0efdb0a38d2466dc9d4b0b85c1e7e2511b
[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 (*this)
44 {
45         ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control"));
46
47         pattern = 0;
48         last_shuttle_request = 0;
49         last_speed_displayed = -99999999;
50         shuttle_grabbed = false;
51         shuttle_fract = 0.0;
52         shuttle_max_speed = 8.0f;
53         shuttle_style_menu = 0;
54         shuttle_unit_menu = 0;
55         shuttle_context_menu = 0;
56
57         set_flags (CAN_FOCUS);
58         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);
59         set_size_request (100, 15);
60         set_name (X_("ShuttleControl"));
61
62         Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
63 }
64
65 ShuttleControl::~ShuttleControl ()
66 {
67         cairo_pattern_destroy (pattern);
68 }
69
70 void
71 ShuttleControl::set_session (Session *s)
72 {
73         SessionHandlePtr::set_session (s);
74
75         if (_session) {
76                 set_sensitive (true);
77         } else {
78                 set_sensitive (false);
79         }
80 }
81
82 void
83 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
84 {
85         if (pattern) {
86                 cairo_pattern_destroy (pattern);
87                 pattern = 0;
88         }
89
90         pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
91         
92         /* add 3 color stops */
93
94         cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
95         cairo_pattern_add_color_stop_rgb (pattern, 0.5, 0.0, 0.0, 1.0);
96         cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
97
98         DrawingArea::on_size_allocate (alloc);
99 }
100
101 void
102 ShuttleControl::map_transport_state ()
103 {
104         float speed = _session->transport_speed ();
105
106         if (speed != 0.0) {
107                 shuttle_fract = SHUTTLE_FRACT_SPEED1;  /* speed = 1.0, believe it or not */
108         } else {
109                 shuttle_fract = 0;
110         }
111
112         queue_draw ();
113 }
114
115 void
116 ShuttleControl::build_shuttle_context_menu ()
117 {
118         using namespace Menu_Helpers;
119
120         shuttle_context_menu = new Menu();
121         MenuList& items = shuttle_context_menu->items();
122
123         Menu* speed_menu = manage (new Menu());
124         MenuList& speed_items = speed_menu->items();
125
126         RadioMenuItem::Group group;
127
128         speed_items.push_back (RadioMenuElem (group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
129         if (shuttle_max_speed == 8.0) {
130                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
131         }
132         speed_items.push_back (RadioMenuElem (group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
133         if (shuttle_max_speed == 6.0) {
134                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
135         }
136         speed_items.push_back (RadioMenuElem (group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
137         if (shuttle_max_speed == 4.0) {
138                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
139         }
140         speed_items.push_back (RadioMenuElem (group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
141         if (shuttle_max_speed == 3.0) {
142                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
143         }
144         speed_items.push_back (RadioMenuElem (group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
145         if (shuttle_max_speed == 2.0) {
146                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
147         }
148         speed_items.push_back (RadioMenuElem (group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
149         if (shuttle_max_speed == 1.5) {
150                 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
151         }
152
153         items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
154         
155         Menu* units_menu = manage (new Menu);
156         MenuList& units_items = units_menu->items();
157         RadioMenuItem::Group units_group;
158         
159         units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
160         if (Config->get_shuttle_units() == Percentage) {
161                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
162         }
163         units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
164         if (Config->get_shuttle_units() == Semitones) {
165                 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
166         }
167         items.push_back (MenuElem (_("Units"), *units_menu));
168         
169         Menu* style_menu = manage (new Menu);
170         MenuList& style_items = style_menu->items();
171         RadioMenuItem::Group style_group;
172
173         style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
174         if (Config->get_shuttle_behaviour() == Sprung) {
175                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
176         }
177         style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
178         if (Config->get_shuttle_behaviour() == Wheel) {
179                 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
180         }
181         
182         items.push_back (MenuElem (_("Mode"), *style_menu));
183 }
184
185 void
186 ShuttleControl::show_shuttle_context_menu ()
187 {
188         if (shuttle_context_menu == 0) {
189                 build_shuttle_context_menu ();
190         }
191
192         shuttle_context_menu->popup (1, gtk_get_current_event_time());
193 }
194
195 void
196 ShuttleControl::set_shuttle_max_speed (float speed)
197 {
198         shuttle_max_speed = speed;
199 }
200
201 bool
202 ShuttleControl::on_button_press_event (GdkEventButton* ev)
203 {
204         if (!_session) {
205                 return true;
206         }
207
208 #if 0
209         if (shuttle_controller_binding_proxy.button_press_handler (ev)) {
210                 return true;
211         }
212 #endif
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), _("stop"));
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 /*
483         set_tip (shuttle_units_button, _("Select semitones or %%-age for speed display"));
484         set_tip (speed_display_box, _("Current transport speed"));
485
486
487 */
488
489 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
490         : PBD::Controllable (X_("Shuttle")) 
491         , sc (s)
492 {
493 }
494
495 void
496 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
497 {
498         _id = str;
499 }
500
501 void
502 ShuttleControl::ShuttleControllable::set_value (double val)
503 {
504         double fract;
505         
506         if (val == 0.5) {
507                 fract = 0.0;
508         } else {
509                 if (val < 0.5) {
510                         fract = -((0.5 - val)/0.5);
511                 } else {
512                         fract = ((val - 0.5)/0.5);
513                 }
514         }
515         
516         sc.set_shuttle_fract (fract);
517 }
518
519 double 
520 ShuttleControl::ShuttleControllable::get_value () const
521 {
522         return sc.get_shuttle_fract ();
523 }
524
525 void
526 ShuttleControl::parameter_changed (std::string p)
527 {
528         if (p == "shuttle-behaviour") {
529                 switch (Config->get_shuttle_behaviour ()) {
530                 case Sprung:
531                         shuttle_fract = 0.0;
532                         if (_session) {
533                                 if (_session->transport_rolling()) {
534                                         shuttle_fract = SHUTTLE_FRACT_SPEED1;
535                                         _session->request_transport_speed (1.0);
536                                 }
537                         }
538                         break;
539                 case Wheel:
540                         break;
541                 }
542                 queue_draw ();
543                         
544         } else if (p == "shuttle-units") {
545                 queue_draw ();
546         }
547 }