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 "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 "shuttle_control.h"
35 using namespace Gtkmm2ext;
36 using namespace ARDOUR;
40 gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer)
45 ShuttleControl::ShuttleControl ()
46 : _controllable (new ShuttleControllable (*this))
47 , binding_proxy (_controllable)
49 ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control (Context-click for options)"));
52 last_shuttle_request = 0;
53 last_speed_displayed = -99999999;
54 shuttle_grabbed = false;
56 shuttle_max_speed = 8.0f;
57 shuttle_style_menu = 0;
58 shuttle_unit_menu = 0;
59 shuttle_context_menu = 0;
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"));
66 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
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));
73 ShuttleControl::~ShuttleControl ()
75 cairo_pattern_destroy (pattern);
79 ShuttleControl::set_session (Session *s)
81 SessionHandlePtr::set_session (s);
85 _session->add_controllable (_controllable);
87 set_sensitive (false);
92 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
95 cairo_pattern_destroy (pattern);
99 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
101 /* add 3 color stops */
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);
107 DrawingArea::on_size_allocate (alloc);
111 ShuttleControl::map_transport_state ()
113 float speed = _session->transport_speed ();
116 if (Config->get_shuttle_units() == Semitones) {
117 shuttle_fract = semitones_as_fract (speed_as_semitones (speed));
119 shuttle_fract = speed/shuttle_max_speed;
129 ShuttleControl::build_shuttle_context_menu ()
131 using namespace Menu_Helpers;
133 shuttle_context_menu = new Menu();
134 MenuList& items = shuttle_context_menu->items();
136 Menu* speed_menu = manage (new Menu());
137 MenuList& speed_items = speed_menu->items();
139 Menu* units_menu = manage (new Menu);
140 MenuList& units_items = units_menu->items();
141 RadioMenuItem::Group units_group;
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();
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();
151 items.push_back (MenuElem (_("Units"), *units_menu));
153 Menu* style_menu = manage (new Menu);
154 MenuList& style_items = style_menu->items();
155 RadioMenuItem::Group style_group;
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();
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();
166 items.push_back (MenuElem (_("Mode"), *style_menu));
168 RadioMenuItem::Group speed_group;
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 ();
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 ();
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 ();
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 ();
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 ();
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 ();
195 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
200 ShuttleControl::show_shuttle_context_menu ()
202 if (shuttle_context_menu == 0) {
203 build_shuttle_context_menu ();
206 shuttle_context_menu->popup (1, gtk_get_current_event_time());
210 ShuttleControl::set_shuttle_max_speed (float speed)
212 shuttle_max_speed = speed;
216 ShuttleControl::on_button_press_event (GdkEventButton* ev)
222 if (binding_proxy.button_press_handler (ev)) {
226 if (Keyboard::is_context_menu_event (ev)) {
227 show_shuttle_context_menu ();
231 switch (ev->button) {
234 shuttle_grabbed = true;
235 mouse_shuttle (ev->x, true);
248 ShuttleControl::on_button_release_event (GdkEventButton* ev)
254 switch (ev->button) {
256 shuttle_grabbed = false;
257 remove_modal_grab ();
259 if (Config->get_shuttle_behaviour() == Sprung) {
260 if (_session->config.get_auto_play()) {
261 _session->request_transport_speed (1.0);
263 _session->request_transport_speed (0.0);
266 mouse_shuttle (ev->x, true);
272 if (_session->transport_rolling()) {
273 _session->request_transport_speed (1.0);
287 ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtr<Gtk::Tooltip>&)
293 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
295 if (!_session || Config->get_shuttle_behaviour() != Wheel) {
299 switch (ev->direction) {
302 case GDK_SCROLL_RIGHT:
303 shuttle_fract += 0.005;
305 case GDK_SCROLL_DOWN:
306 case GDK_SCROLL_LEFT:
307 shuttle_fract -= 0.005;
313 use_shuttle_fract (true);
319 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
321 if (!_session || !shuttle_grabbed) {
325 return mouse_shuttle (ev->x, false);
329 ShuttleControl::mouse_shuttle (double x, bool force)
331 double const center = get_width() / 2.0;
332 double distance_from_center = x - center;
334 if (distance_from_center > 0) {
335 distance_from_center = min (distance_from_center, center);
337 distance_from_center = max (distance_from_center, -center);
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
345 shuttle_fract = distance_from_center / center; // center == half the width
346 use_shuttle_fract (force);
351 ShuttleControl::set_shuttle_fract (double f)
354 use_shuttle_fract (false);
358 ShuttleControl::speed_as_semitones (float speed)
361 return (int) round (12.0 * fast_log2 (-speed));
363 return (int) round (12.0 * fast_log2 (speed));
368 ShuttleControl::semitones_as_speed (int semi)
370 return pow (2.0, (semi / 12.0));
374 ShuttleControl::semitones_as_fract (int semi)
376 double const step = 1.0 / 24.0; // range is 24 semitones up & down
381 ShuttleControl::fract_as_semitones (float fract)
383 double const step = 1.0 / 24.0; // range is 24 semitones up & down
384 return (int) round (shuttle_fract / step);
388 ShuttleControl::use_shuttle_fract (bool force)
390 microseconds_t now = get_microseconds();
392 if (Config->get_shuttle_units() == Semitones) {
393 shuttle_fract = max (-1.0f, shuttle_fract);
394 shuttle_fract = min (1.0f, shuttle_fract);
396 shuttle_fract = max (-shuttle_max_speed, shuttle_fract);
397 shuttle_fract = min (shuttle_max_speed, shuttle_fract);
400 /* do not attempt to submit a motion-driven transport speed request
401 more than once per process cycle.
404 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
408 last_shuttle_request = now;
412 if (Config->get_shuttle_units() == Semitones) {
413 speed = semitones_as_speed (fract_as_semitones (shuttle_fract));
415 speed = shuttle_max_speed * shuttle_fract;
418 _session->request_transport_speed_nonzero (speed);
422 ShuttleControl::on_expose_event (GdkEventExpose* event)
424 cairo_text_extents_t extents;
425 Glib::RefPtr<Gdk::Window> win (get_window());
426 Glib::RefPtr<Gtk::Style> style (get_style());
428 cairo_t* cr = gdk_cairo_create (win->gobj());
430 cairo_set_source (cr, pattern);
431 cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
432 cairo_fill_preserve (cr);
434 cairo_set_source_rgb (cr, 0, 0, 0.0);
440 speed = _session->transport_speed ();
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, 1);
448 cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
449 cairo_line_to (cr, x, get_height()-1);
456 if (Config->get_shuttle_units() == Percentage) {
458 snprintf (buf, sizeof (buf), _("Playing"));
460 snprintf (buf, sizeof (buf), "%d%%", (int) round (speed * 100));
463 snprintf (buf, sizeof (buf), "%+d semitones", speed_as_semitones (speed));
466 snprintf (buf, sizeof (buf), _("Stopped"));
469 last_speed_displayed = speed;
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);
479 switch (Config->get_shuttle_behaviour()) {
481 snprintf (buf, sizeof (buf), _("Sprung"));
484 snprintf (buf, sizeof (buf), _("Wheel"));
488 cairo_text_extents (cr, buf, &extents);
490 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
491 cairo_show_text (cr, buf);
499 ShuttleControl::shuttle_unit_clicked ()
501 if (shuttle_unit_menu == 0) {
502 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
504 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
508 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
510 Config->set_shuttle_behaviour (s);
514 ShuttleControl::set_shuttle_units (ShuttleUnits s)
516 Config->set_shuttle_units (s);
520 ShuttleControl::update_speed_display ()
522 if (_session->transport_speed() != last_speed_displayed) {
527 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
528 : PBD::Controllable (X_("Shuttle"))
534 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
540 ShuttleControl::ShuttleControllable::set_value (double val)
548 fract = -((0.5 - val)/0.5);
550 fract = ((val - 0.5)/0.5);
554 sc.set_shuttle_fract (fract);
558 ShuttleControl::ShuttleControllable::get_value () const
560 return sc.get_shuttle_fract ();
564 ShuttleControl::parameter_changed (std::string p)
566 if (p == "shuttle-behaviour") {
567 switch (Config->get_shuttle_behaviour ()) {
569 /* back to Sprung - reset to speed = 1.0 if playing
572 if (_session->transport_rolling()) {
573 if (_session->transport_speed() == 1.0) {
576 _session->request_transport_speed (1.0);
577 /* redraw when speed changes */
590 } else if (p == "shuttle-units") {