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.
21 #include <cairo/cairo.h>
23 #include <pbd/stacktrace.h>
25 #include "ardour/ardour.h"
26 #include "ardour/audioengine.h"
27 #include "ardour/rc_configuration.h"
28 #include "ardour/session.h"
30 #include "gtkmm2ext/keyboard.h"
31 #include "gtkmm2ext/gui_thread.h"
33 #include "ardour_ui.h"
34 #include "shuttle_control.h"
37 using namespace Gtkmm2ext;
38 using namespace ARDOUR;
42 ShuttleControl::ShuttleControl ()
43 : _controllable (new ShuttleControllable (*this))
44 , binding_proxy (_controllable)
46 ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
49 last_shuttle_request = 0;
50 last_speed_displayed = -99999999;
51 shuttle_grabbed = false;
53 shuttle_max_speed = 8.0f;
54 shuttle_style_menu = 0;
55 shuttle_unit_menu = 0;
56 shuttle_context_menu = 0;
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"));
63 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
65 signal_query_tooltip().connect (sigc::mem_fun (*this, &ShuttleControl::on_query_tooltip));
68 ShuttleControl::~ShuttleControl ()
70 cairo_pattern_destroy (pattern);
74 ShuttleControl::set_session (Session *s)
76 SessionHandlePtr::set_session (s);
80 _session->add_controllable (_controllable);
82 set_sensitive (false);
87 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
90 cairo_pattern_destroy (pattern);
94 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
96 /* add 3 color stops */
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);
102 DrawingArea::on_size_allocate (alloc);
106 ShuttleControl::map_transport_state ()
108 float speed = _session->transport_speed ();
111 if (Config->get_shuttle_units() == Semitones) {
112 shuttle_fract = speed/2.0;
114 shuttle_fract = speed/shuttle_max_speed;
124 ShuttleControl::build_shuttle_context_menu ()
126 using namespace Menu_Helpers;
128 shuttle_context_menu = new Menu();
129 MenuList& items = shuttle_context_menu->items();
131 Menu* speed_menu = manage (new Menu());
132 MenuList& speed_items = speed_menu->items();
134 Menu* units_menu = manage (new Menu);
135 MenuList& units_items = units_menu->items();
136 RadioMenuItem::Group units_group;
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();
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();
146 items.push_back (MenuElem (_("Units"), *units_menu));
148 Menu* style_menu = manage (new Menu);
149 MenuList& style_items = style_menu->items();
150 RadioMenuItem::Group style_group;
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();
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();
161 items.push_back (MenuElem (_("Mode"), *style_menu));
163 RadioMenuItem::Group speed_group;
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 ();
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 ();
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 ();
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 ();
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 ();
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 ();
190 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
195 ShuttleControl::show_shuttle_context_menu ()
197 if (shuttle_context_menu == 0) {
198 build_shuttle_context_menu ();
201 shuttle_context_menu->popup (1, gtk_get_current_event_time());
205 ShuttleControl::set_shuttle_max_speed (float speed)
207 shuttle_max_speed = speed;
211 ShuttleControl::on_button_press_event (GdkEventButton* ev)
217 if (binding_proxy.button_press_handler (ev)) {
221 if (Keyboard::is_context_menu_event (ev)) {
222 show_shuttle_context_menu ();
226 switch (ev->button) {
229 shuttle_grabbed = true;
230 mouse_shuttle (ev->x, true);
243 ShuttleControl::on_button_release_event (GdkEventButton* ev)
249 switch (ev->button) {
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);
260 _session->request_transport_speed (0.0);
267 if (_session->transport_rolling()) {
268 shuttle_fract = SHUTTLE_FRACT_SPEED1;
269 _session->request_transport_speed (1.0);
282 use_shuttle_fract (true);
288 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
290 std::cerr << "OQT!\n";
295 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
297 if (!_session || Config->get_shuttle_behaviour() != Wheel) {
301 switch (ev->direction) {
304 case GDK_SCROLL_RIGHT:
305 shuttle_fract += 0.005;
307 case GDK_SCROLL_DOWN:
308 case GDK_SCROLL_LEFT:
309 shuttle_fract -= 0.005;
315 use_shuttle_fract (true);
321 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
323 if (!_session || !shuttle_grabbed) {
327 return mouse_shuttle (ev->x, false);
331 ShuttleControl::mouse_shuttle (double x, bool force)
333 double const half_width = get_width() / 2.0;
334 double distance = x - half_width;
337 distance = min (distance, half_width);
339 distance = max (distance, -half_width);
342 shuttle_fract = distance / half_width;
343 use_shuttle_fract (force);
348 ShuttleControl::set_shuttle_fract (double f)
351 use_shuttle_fract (false);
355 ShuttleControl::use_shuttle_fract (bool force)
357 microseconds_t now = get_microseconds();
359 /* do not attempt to submit a motion-driven transport speed request
360 more than once per process cycle.
363 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
367 last_shuttle_request = now;
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));
376 speed = shuttle_max_speed * shuttle_fract;
379 _session->request_transport_speed_nonzero (speed);
383 ShuttleControl::on_expose_event (GdkEventExpose* event)
385 cairo_text_extents_t extents;
386 Glib::RefPtr<Gdk::Window> win (get_window());
387 Glib::RefPtr<Gtk::Style> style (get_style());
389 cairo_t* cr = gdk_cairo_create (win->gobj());
391 cairo_set_source (cr, pattern);
392 cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
393 cairo_fill_preserve (cr);
395 cairo_set_source_rgb (cr, 0, 0, 0.0);
401 speed = _session->transport_speed ();
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());
417 if (Config->get_shuttle_units() == Percentage) {
419 snprintf (buf, sizeof (buf), _("Playing"));
421 snprintf (buf, sizeof (buf), "%d%%", (int) round (speed * 100));
425 snprintf (buf, sizeof (buf), "-%d semitones", (int) round (12.0 * fast_log2 (-speed)));
427 snprintf (buf, sizeof (buf), "+%d semitones", (int) round (12.0 * fast_log2 (speed)));
431 snprintf (buf, sizeof (buf), _("Stopped"));
434 last_speed_displayed = speed;
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);
444 switch (Config->get_shuttle_behaviour()) {
446 snprintf (buf, sizeof (buf), _("Sprung"));
449 snprintf (buf, sizeof (buf), _("Wheel"));
453 cairo_text_extents (cr, buf, &extents);
455 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
456 cairo_show_text (cr, buf);
464 ShuttleControl::shuttle_unit_clicked ()
466 if (shuttle_unit_menu == 0) {
467 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
469 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
473 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
475 Config->set_shuttle_behaviour (s);
479 ShuttleControl::set_shuttle_units (ShuttleUnits s)
481 Config->set_shuttle_units (s);
485 ShuttleControl::update_speed_display ()
487 if (_session->transport_speed() != last_speed_displayed) {
492 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
493 : PBD::Controllable (X_("Shuttle"))
499 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
505 ShuttleControl::ShuttleControllable::set_value (double val)
513 fract = -((0.5 - val)/0.5);
515 fract = ((val - 0.5)/0.5);
519 sc.set_shuttle_fract (fract);
523 ShuttleControl::ShuttleControllable::get_value () const
525 return sc.get_shuttle_fract ();
529 ShuttleControl::parameter_changed (std::string p)
531 if (p == "shuttle-behaviour") {
532 switch (Config->get_shuttle_behaviour ()) {
534 /* back to Sprung - reset to speed = 1.0 if playing
537 if (_session->transport_rolling()) {
538 if (_session->transport_speed() == 1.0) {
541 _session->request_transport_speed (1.0);
542 /* redraw when speed changes */
555 } else if (p == "shuttle-units") {