Set mixbufs count in the case of a panner being used, as
[ardour2.git] / gtk2_ardour / shuttle_control.cc
blobdd7cd753e67719da91e3c59c11c6cc9aab5c4e6f
1 /*
2 Copyright (C) 2011 Paul Davis
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.
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.
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.
19 #include <algorithm>
21 #include <cairo/cairo.h>
23 #include "ardour/ardour.h"
24 #include "ardour/audioengine.h"
25 #include "ardour/rc_configuration.h"
26 #include "ardour/session.h"
28 #include "gtkmm2ext/keyboard.h"
29 #include "gtkmm2ext/gui_thread.h"
31 #include "ardour_ui.h"
32 #include "rgb_macros.h"
33 #include "shuttle_control.h"
35 #include "i18n.h"
37 using namespace Gtk;
38 using namespace Gtkmm2ext;
39 using namespace ARDOUR;
40 using std::min;
41 using std::max;
43 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
45 return FALSE;
48 ShuttleControl::ShuttleControl ()
49 : _controllable (new ShuttleControllable (*this))
50 , binding_proxy (_controllable)
52 ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
54 pattern = 0;
55 last_shuttle_request = 0;
56 last_speed_displayed = -99999999;
57 shuttle_grabbed = false;
58 shuttle_speed_on_grab = 0;
59 shuttle_fract = 0.0;
60 shuttle_max_speed = 8.0f;
61 shuttle_style_menu = 0;
62 shuttle_unit_menu = 0;
63 shuttle_context_menu = 0;
65 set_flags (CAN_FOCUS);
66 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);
67 set_size_request (100, 15);
68 set_name (X_("ShuttleControl"));
70 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
72 /* gtkmm 2.4: the C++ wrapper doesn't work */
73 g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL);
74 // signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
77 ShuttleControl::~ShuttleControl ()
79 cairo_pattern_destroy (pattern);
82 void
83 ShuttleControl::set_session (Session *s)
85 SessionHandlePtr::set_session (s);
87 if (_session) {
88 set_sensitive (true);
89 _session->add_controllable (_controllable);
90 } else {
91 set_sensitive (false);
95 void
96 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
98 if (pattern) {
99 cairo_pattern_destroy (pattern);
100 pattern = 0;
103 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
105 /* add 3 color stops */
107 uint32_t col = ARDOUR_UI::config()->canvasvar_Shuttle.get();
109 int r,b,g,a;
110 UINT_TO_RGBA(col, &r, &g, &b, &a);
112 cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
113 cairo_pattern_add_color_stop_rgb (pattern, 0.5, r/255.0, g/255.0, b/255.0);
114 cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
116 DrawingArea::on_size_allocate (alloc);
119 void
120 ShuttleControl::map_transport_state ()
122 float speed = _session->transport_speed ();
124 if (fabs(speed) <= (2*DBL_EPSILON)) {
125 shuttle_fract = 0;
126 } else {
127 if (Config->get_shuttle_units() == Semitones) {
128 bool reverse;
129 int semi = speed_as_semitones (speed, reverse);
130 shuttle_fract = semitones_as_fract (semi, reverse);
131 } else {
132 shuttle_fract = speed/shuttle_max_speed;
136 queue_draw ();
139 void
140 ShuttleControl::build_shuttle_context_menu ()
142 using namespace Menu_Helpers;
144 shuttle_context_menu = new Menu();
145 MenuList& items = shuttle_context_menu->items();
147 Menu* speed_menu = manage (new Menu());
148 MenuList& speed_items = speed_menu->items();
150 Menu* units_menu = manage (new Menu);
151 MenuList& units_items = units_menu->items();
152 RadioMenuItem::Group units_group;
154 units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
155 if (Config->get_shuttle_units() == Percentage) {
156 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
158 units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
159 if (Config->get_shuttle_units() == Semitones) {
160 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
162 items.push_back (MenuElem (_("Units"), *units_menu));
164 Menu* style_menu = manage (new Menu);
165 MenuList& style_items = style_menu->items();
166 RadioMenuItem::Group style_group;
168 style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
169 if (Config->get_shuttle_behaviour() == Sprung) {
170 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
172 style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
173 if (Config->get_shuttle_behaviour() == Wheel) {
174 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
177 items.push_back (MenuElem (_("Mode"), *style_menu));
179 RadioMenuItem::Group speed_group;
181 speed_items.push_back (RadioMenuElem (speed_group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
182 if (shuttle_max_speed == 8.0) {
183 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
185 speed_items.push_back (RadioMenuElem (speed_group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
186 if (shuttle_max_speed == 6.0) {
187 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
189 speed_items.push_back (RadioMenuElem (speed_group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
190 if (shuttle_max_speed == 4.0) {
191 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
193 speed_items.push_back (RadioMenuElem (speed_group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
194 if (shuttle_max_speed == 3.0) {
195 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
197 speed_items.push_back (RadioMenuElem (speed_group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
198 if (shuttle_max_speed == 2.0) {
199 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
201 speed_items.push_back (RadioMenuElem (speed_group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
202 if (shuttle_max_speed == 1.5) {
203 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
206 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
210 void
211 ShuttleControl::show_shuttle_context_menu ()
213 if (shuttle_context_menu == 0) {
214 build_shuttle_context_menu ();
217 shuttle_context_menu->popup (1, gtk_get_current_event_time());
220 void
221 ShuttleControl::set_shuttle_max_speed (float speed)
223 shuttle_max_speed = speed;
226 bool
227 ShuttleControl::on_button_press_event (GdkEventButton* ev)
229 if (!_session) {
230 return true;
233 if (binding_proxy.button_press_handler (ev)) {
234 return true;
237 if (Keyboard::is_context_menu_event (ev)) {
238 show_shuttle_context_menu ();
239 return true;
242 switch (ev->button) {
243 case 1:
244 add_modal_grab ();
245 shuttle_grabbed = true;
246 shuttle_speed_on_grab = _session->transport_speed ();
247 mouse_shuttle (ev->x, true);
248 break;
250 case 2:
251 case 3:
252 return true;
253 break;
256 return true;
259 bool
260 ShuttleControl::on_button_release_event (GdkEventButton* ev)
262 if (!_session) {
263 return true;
266 switch (ev->button) {
267 case 1:
268 shuttle_grabbed = false;
269 remove_modal_grab ();
271 if (Config->get_shuttle_behaviour() == Sprung) {
272 _session->request_transport_speed (shuttle_speed_on_grab);
273 } else {
274 mouse_shuttle (ev->x, true);
277 return true;
279 case 2:
280 if (_session->transport_rolling()) {
281 _session->request_transport_speed (1.0);
283 return true;
285 case 3:
286 default:
287 return true;
291 return true;
294 bool
295 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
297 return false;
300 bool
301 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
303 if (!_session || Config->get_shuttle_behaviour() != Wheel) {
304 return true;
307 bool semis = (Config->get_shuttle_units() == Semitones);
309 switch (ev->direction) {
310 case GDK_SCROLL_UP:
311 case GDK_SCROLL_RIGHT:
312 if (semis) {
313 if (shuttle_fract == 0) {
314 shuttle_fract = semitones_as_fract (1, false);
315 } else {
316 bool rev;
317 int st = fract_as_semitones (shuttle_fract, rev);
318 shuttle_fract = semitones_as_fract (st + 1, rev);
320 } else {
321 shuttle_fract += 0.00125;
323 break;
324 case GDK_SCROLL_DOWN:
325 case GDK_SCROLL_LEFT:
326 if (semis) {
327 if (shuttle_fract == 0) {
328 shuttle_fract = semitones_as_fract (1, true);
329 } else {
330 bool rev;
331 int st = fract_as_semitones (shuttle_fract, rev);
332 shuttle_fract = semitones_as_fract (st - 1, rev);
334 } else {
335 shuttle_fract -= 0.00125;
337 break;
338 default:
339 return false;
342 if (semis) {
344 float lower_side_of_dead_zone = semitones_as_fract (-24, true);
345 float upper_side_of_dead_zone = semitones_as_fract (-24, false);
347 /* if we entered the "dead zone" (-24 semitones in forward or reverse), jump
348 to the far side of it.
351 if (shuttle_fract > lower_side_of_dead_zone && shuttle_fract < upper_side_of_dead_zone) {
352 switch (ev->direction) {
353 case GDK_SCROLL_UP:
354 case GDK_SCROLL_RIGHT:
355 shuttle_fract = upper_side_of_dead_zone;
356 break;
357 case GDK_SCROLL_DOWN:
358 case GDK_SCROLL_LEFT:
359 shuttle_fract = lower_side_of_dead_zone;
360 break;
361 default:
362 /* impossible, checked above */
363 return false;
368 use_shuttle_fract (true);
370 return true;
373 bool
374 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
376 if (!_session || !shuttle_grabbed) {
377 return true;
380 return mouse_shuttle (ev->x, false);
383 gint
384 ShuttleControl::mouse_shuttle (double x, bool force)
386 double const center = get_width() / 2.0;
387 double distance_from_center = x - center;
389 if (distance_from_center > 0) {
390 distance_from_center = min (distance_from_center, center);
391 } else {
392 distance_from_center = max (distance_from_center, -center);
395 /* compute shuttle fract as expressing how far between the center
396 and the edge we are. positive values indicate we are right of
397 center, negative values indicate left of center
400 shuttle_fract = distance_from_center / center; // center == half the width
401 use_shuttle_fract (force);
402 return true;
405 void
406 ShuttleControl::set_shuttle_fract (double f)
408 shuttle_fract = f;
409 use_shuttle_fract (false);
413 ShuttleControl::speed_as_semitones (float speed, bool& reverse)
415 assert (speed != 0.0);
417 if (speed < 0.0) {
418 reverse = true;
419 return (int) round (12.0 * fast_log2 (-speed));
420 } else {
421 reverse = false;
422 return (int) round (12.0 * fast_log2 (speed));
426 float
427 ShuttleControl::semitones_as_speed (int semi, bool reverse)
429 if (reverse) {
430 return -pow (2.0, (semi / 12.0));
431 } else {
432 return pow (2.0, (semi / 12.0));
436 float
437 ShuttleControl::semitones_as_fract (int semi, bool reverse)
439 float speed = semitones_as_speed (semi, reverse);
440 return speed/4.0; /* 4.0 is the maximum speed for a 24 semitone shift */
444 ShuttleControl::fract_as_semitones (float fract, bool& reverse)
446 assert (fract != 0.0);
447 return speed_as_semitones (fract * 4.0, reverse);
450 void
451 ShuttleControl::use_shuttle_fract (bool force)
453 microseconds_t now = get_microseconds();
455 shuttle_fract = max (-1.0f, shuttle_fract);
456 shuttle_fract = min (1.0f, shuttle_fract);
458 /* do not attempt to submit a motion-driven transport speed request
459 more than once per process cycle.
462 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
463 return;
466 last_shuttle_request = now;
468 double speed = 0;
470 if (Config->get_shuttle_units() == Semitones) {
471 if (shuttle_fract != 0.0) {
472 bool reverse;
473 int semi = fract_as_semitones (shuttle_fract, reverse);
474 speed = semitones_as_speed (semi, reverse);
475 } else {
476 speed = 0.0;
478 } else {
479 speed = shuttle_max_speed * shuttle_fract;
482 _session->request_transport_speed_nonzero (speed);
485 bool
486 ShuttleControl::on_expose_event (GdkEventExpose* event)
488 cairo_text_extents_t extents;
489 Glib::RefPtr<Gdk::Window> win (get_window());
490 Glib::RefPtr<Gtk::Style> style (get_style());
492 cairo_t* cr = gdk_cairo_create (win->gobj());
494 cairo_set_source (cr, pattern);
495 cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
496 cairo_fill_preserve (cr);
498 cairo_set_source_rgb (cr, 0, 0, 0.0);
499 cairo_stroke (cr);
501 float speed = 0.0;
503 if (_session) {
504 speed = _session->transport_speed ();
507 /* Marker */
509 double visual_fraction = std::min (1.0f, speed/shuttle_max_speed);
510 double x = (get_width() / 2.0) + (0.5 * (get_width() * visual_fraction));
511 cairo_move_to (cr, x, 1);
512 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
513 cairo_line_to (cr, x, get_height()-1);
514 cairo_stroke (cr);
516 /* speed text */
518 char buf[32];
520 if (speed != 0) {
522 if (Config->get_shuttle_units() == Percentage) {
524 if (speed == 1.0) {
525 snprintf (buf, sizeof (buf), _("Playing"));
526 } else {
527 if (speed < 0.0) {
528 snprintf (buf, sizeof (buf), "<<< %d%%", (int) round (-speed * 100));
529 } else {
530 snprintf (buf, sizeof (buf), ">>> %d%%", (int) round (speed * 100));
534 } else {
536 bool reversed;
537 int semi = speed_as_semitones (speed, reversed);
539 if (reversed) {
540 snprintf (buf, sizeof (buf), _("<<< %+d semitones"), semi);
541 } else {
542 snprintf (buf, sizeof (buf), _(">>> %+d semitones"), semi);
546 } else {
547 snprintf (buf, sizeof (buf), _("Stopped"));
550 last_speed_displayed = speed;
552 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
553 cairo_text_extents (cr, buf, &extents);
554 cairo_move_to (cr, 10, extents.height + 2);
555 cairo_show_text (cr, buf);
557 /* style text */
560 switch (Config->get_shuttle_behaviour()) {
561 case Sprung:
562 snprintf (buf, sizeof (buf), _("Sprung"));
563 break;
564 case Wheel:
565 snprintf (buf, sizeof (buf), _("Wheel"));
566 break;
569 cairo_text_extents (cr, buf, &extents);
571 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
572 cairo_show_text (cr, buf);
574 cairo_destroy (cr);
576 return true;
579 void
580 ShuttleControl::shuttle_unit_clicked ()
582 if (shuttle_unit_menu == 0) {
583 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
585 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
588 void
589 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
591 Config->set_shuttle_behaviour (s);
594 void
595 ShuttleControl::set_shuttle_units (ShuttleUnits s)
597 Config->set_shuttle_units (s);
600 void
601 ShuttleControl::update_speed_display ()
603 if (_session->transport_speed() != last_speed_displayed) {
604 queue_draw ();
608 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
609 : PBD::Controllable (X_("Shuttle"))
610 , sc (s)
614 void
615 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
617 _id = str;
620 void
621 ShuttleControl::ShuttleControllable::set_value (double val)
623 double fract;
625 if (val == 0.5) {
626 fract = 0.0;
627 } else {
628 if (val < 0.5) {
629 fract = -((0.5 - val)/0.5);
630 } else {
631 fract = ((val - 0.5)/0.5);
635 sc.set_shuttle_fract (fract);
638 double
639 ShuttleControl::ShuttleControllable::get_value () const
641 return sc.get_shuttle_fract ();
644 void
645 ShuttleControl::parameter_changed (std::string p)
647 if (p == "shuttle-behaviour") {
648 switch (Config->get_shuttle_behaviour ()) {
649 case Sprung:
650 /* back to Sprung - reset to speed = 1.0 if playing
652 if (_session) {
653 if (_session->transport_rolling()) {
654 if (_session->transport_speed() == 1.0) {
655 queue_draw ();
656 } else {
657 _session->request_transport_speed (1.0);
658 /* redraw when speed changes */
660 } else {
661 queue_draw ();
664 break;
666 case Wheel:
667 queue_draw ();
668 break;
671 } else if (p == "shuttle-units") {
672 queue_draw ();