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 (*this)
45 ARDOUR_UI::instance()->set_tip (*this, _("Shuttle speed control"));
48 last_shuttle_request = 0;
49 last_speed_displayed = -99999999;
50 shuttle_grabbed = false;
52 shuttle_max_speed = 8.0f;
53 shuttle_style_menu = 0;
54 shuttle_unit_menu = 0;
55 shuttle_context_menu = 0;
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"));
62 Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, ui_bind (&ShuttleControl::parameter_changed, this, _1), gui_context());
65 ShuttleControl::~ShuttleControl ()
67 cairo_pattern_destroy (pattern);
71 ShuttleControl::set_session (Session *s)
73 SessionHandlePtr::set_session (s);
78 set_sensitive (false);
83 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
86 cairo_pattern_destroy (pattern);
90 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
92 /* add 3 color stops */
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);
98 DrawingArea::on_size_allocate (alloc);
102 ShuttleControl::map_transport_state ()
104 float speed = _session->transport_speed ();
107 shuttle_fract = SHUTTLE_FRACT_SPEED1; /* speed = 1.0, believe it or not */
116 ShuttleControl::build_shuttle_context_menu ()
118 using namespace Menu_Helpers;
120 shuttle_context_menu = new Menu();
121 MenuList& items = shuttle_context_menu->items();
123 Menu* speed_menu = manage (new Menu());
124 MenuList& speed_items = speed_menu->items();
126 RadioMenuItem::Group group;
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 ();
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 ();
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 ();
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 ();
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 ();
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 ();
153 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
155 Menu* units_menu = manage (new Menu);
156 MenuList& units_items = units_menu->items();
157 RadioMenuItem::Group units_group;
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();
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();
167 items.push_back (MenuElem (_("Units"), *units_menu));
169 Menu* style_menu = manage (new Menu);
170 MenuList& style_items = style_menu->items();
171 RadioMenuItem::Group style_group;
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();
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();
182 items.push_back (MenuElem (_("Mode"), *style_menu));
186 ShuttleControl::show_shuttle_context_menu ()
188 if (shuttle_context_menu == 0) {
189 build_shuttle_context_menu ();
192 shuttle_context_menu->popup (1, gtk_get_current_event_time());
196 ShuttleControl::set_shuttle_max_speed (float speed)
198 shuttle_max_speed = speed;
202 ShuttleControl::on_button_press_event (GdkEventButton* ev)
209 if (shuttle_controller_binding_proxy.button_press_handler (ev)) {
214 if (Keyboard::is_context_menu_event (ev)) {
215 show_shuttle_context_menu ();
219 switch (ev->button) {
222 shuttle_grabbed = true;
223 mouse_shuttle (ev->x, true);
236 ShuttleControl::on_button_release_event (GdkEventButton* ev)
242 switch (ev->button) {
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);
253 _session->request_transport_speed (0.0);
260 if (_session->transport_rolling()) {
261 shuttle_fract = SHUTTLE_FRACT_SPEED1;
262 _session->request_transport_speed (1.0);
275 use_shuttle_fract (true);
281 ShuttleControl::on_scroll_event (GdkEventScroll* ev)
287 switch (ev->direction) {
290 shuttle_fract += 0.005;
292 case GDK_SCROLL_DOWN:
293 shuttle_fract -= 0.005;
296 /* scroll left/right */
300 use_shuttle_fract (true);
306 ShuttleControl::on_motion_notify_event (GdkEventMotion* ev)
308 if (!_session || !shuttle_grabbed) {
312 return mouse_shuttle (ev->x, false);
316 ShuttleControl::mouse_shuttle (double x, bool force)
318 double const half_width = get_width() / 2.0;
319 double distance = x - half_width;
322 distance = min (distance, half_width);
324 distance = max (distance, -half_width);
327 shuttle_fract = distance / half_width;
328 use_shuttle_fract (force);
333 ShuttleControl::set_shuttle_fract (double f)
336 use_shuttle_fract (false);
340 ShuttleControl::use_shuttle_fract (bool force)
342 microseconds_t now = get_microseconds();
344 /* do not attempt to submit a motion-driven transport speed request
345 more than once per process cycle.
348 if (!force && (last_shuttle_request - now) < (microseconds_t) AudioEngine::instance()->usecs_per_cycle()) {
352 last_shuttle_request = now;
356 if (Config->get_shuttle_units() == Semitones) {
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));
364 bool const neg = (shuttle_fract < 0.0);
365 double fract = 1 - sqrt (1 - (shuttle_fract * shuttle_fract)); // Formula A1
371 speed = shuttle_max_speed * fract;
374 _session->request_transport_speed_nonzero (speed);
378 ShuttleControl::on_expose_event (GdkEventExpose* event)
380 cairo_text_extents_t extents;
381 Glib::RefPtr<Gdk::Window> win (get_window());
382 Glib::RefPtr<Gtk::Style> style (get_style());
384 cairo_t* cr = gdk_cairo_create (win->gobj());
386 cairo_set_source (cr, pattern);
387 cairo_rectangle (cr, 0.0, 0.0, get_width(), get_height());
388 cairo_fill_preserve (cr);
390 cairo_set_source_rgb (cr, 0, 0, 0.0);
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());
407 speed = _session->transport_speed ();
411 if (Config->get_shuttle_units() == Percentage) {
412 snprintf (buf, sizeof (buf), "%d%%", (int) round (speed * 100));
415 snprintf (buf, sizeof (buf), "< %d semitones", (int) round (12.0 * fast_log2 (-speed)));
417 snprintf (buf, sizeof (buf), "> %d semitones", (int) round (12.0 * fast_log2 (speed)));
421 snprintf (buf, sizeof (buf), _("stop"));
424 last_speed_displayed = speed;
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);
434 switch (Config->get_shuttle_behaviour()) {
436 snprintf (buf, sizeof (buf), _("Sprung"));
439 snprintf (buf, sizeof (buf), _("Wheel"));
443 cairo_text_extents (cr, buf, &extents);
445 cairo_move_to (cr, get_width() - (fabs(extents.x_advance) + 5), extents.height + 2);
446 cairo_show_text (cr, buf);
454 ShuttleControl::shuttle_unit_clicked ()
456 if (shuttle_unit_menu == 0) {
457 shuttle_unit_menu = dynamic_cast<Menu*> (ActionManager::get_widget ("/ShuttleUnitPopup"));
459 shuttle_unit_menu->popup (1, gtk_get_current_event_time());
463 ShuttleControl::set_shuttle_style (ShuttleBehaviour s)
465 Config->set_shuttle_behaviour (s);
469 ShuttleControl::set_shuttle_units (ShuttleUnits s)
471 Config->set_shuttle_units (s);
475 ShuttleControl::update_speed_display ()
477 if (_session->transport_speed() != last_speed_displayed) {
483 set_tip (shuttle_units_button, _("Select semitones or %%-age for speed display"));
484 set_tip (speed_display_box, _("Current transport speed"));
489 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
490 : PBD::Controllable (X_("Shuttle"))
496 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
502 ShuttleControl::ShuttleControllable::set_value (double val)
510 fract = -((0.5 - val)/0.5);
512 fract = ((val - 0.5)/0.5);
516 sc.set_shuttle_fract (fract);
520 ShuttleControl::ShuttleControllable::get_value () const
522 return sc.get_shuttle_fract ();
526 ShuttleControl::parameter_changed (std::string p)
528 if (p == "shuttle-behaviour") {
529 switch (Config->get_shuttle_behaviour ()) {
533 if (_session->transport_rolling()) {
534 shuttle_fract = SHUTTLE_FRACT_SPEED1;
535 _session->request_transport_speed (1.0);
544 } else if (p == "shuttle-units") {