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());
66 ShuttleControl::~ShuttleControl ()
68 cairo_pattern_destroy (pattern);
72 ShuttleControl::set_session (Session *s)
74 SessionHandlePtr::set_session (s);
78 _session->add_controllable (_controllable);
80 set_sensitive (false);
85 ShuttleControl::on_size_allocate (Gtk::Allocation& alloc)
88 cairo_pattern_destroy (pattern);
92 pattern = cairo_pattern_create_linear (0, 0, alloc.get_width(), alloc.get_height());
94 /* add 3 color stops */
96 cairo_pattern_add_color_stop_rgb (pattern, 0.0, 0, 0, 0);
97 cairo_pattern_add_color_stop_rgb (pattern, 0.5, 0.0, 0.0, 1.0);
98 cairo_pattern_add_color_stop_rgb (pattern, 1.0, 0, 0, 0);
100 DrawingArea::on_size_allocate (alloc);
104 ShuttleControl::map_transport_state ()
106 float speed = _session->transport_speed ();
109 shuttle_fract = SHUTTLE_FRACT_SPEED1; /* speed = 1.0, believe it or not */
118 ShuttleControl::build_shuttle_context_menu ()
120 using namespace Menu_Helpers;
122 shuttle_context_menu = new Menu();
123 MenuList& items = shuttle_context_menu->items();
125 Menu* speed_menu = manage (new Menu());
126 MenuList& speed_items = speed_menu->items();
128 RadioMenuItem::Group group;
130 speed_items.push_back (RadioMenuElem (group, "8", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 8.0f)));
131 if (shuttle_max_speed == 8.0) {
132 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
134 speed_items.push_back (RadioMenuElem (group, "6", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 6.0f)));
135 if (shuttle_max_speed == 6.0) {
136 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
138 speed_items.push_back (RadioMenuElem (group, "4", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 4.0f)));
139 if (shuttle_max_speed == 4.0) {
140 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
142 speed_items.push_back (RadioMenuElem (group, "3", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 3.0f)));
143 if (shuttle_max_speed == 3.0) {
144 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
146 speed_items.push_back (RadioMenuElem (group, "2", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 2.0f)));
147 if (shuttle_max_speed == 2.0) {
148 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
150 speed_items.push_back (RadioMenuElem (group, "1.5", sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_max_speed), 1.5f)));
151 if (shuttle_max_speed == 1.5) {
152 static_cast<RadioMenuItem*>(&speed_items.back())->set_active ();
155 items.push_back (MenuElem (_("Maximum speed"), *speed_menu));
157 Menu* units_menu = manage (new Menu);
158 MenuList& units_items = units_menu->items();
159 RadioMenuItem::Group units_group;
161 units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage)));
162 if (Config->get_shuttle_units() == Percentage) {
163 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
165 units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones)));
166 if (Config->get_shuttle_units() == Semitones) {
167 static_cast<RadioMenuItem*>(&units_items.back())->set_active();
169 items.push_back (MenuElem (_("Units"), *units_menu));
171 Menu* style_menu = manage (new Menu);
172 MenuList& style_items = style_menu->items();
173 RadioMenuItem::Group style_group;
175 style_items.push_back (RadioMenuElem (style_group, _("Sprung"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Sprung)));
176 if (Config->get_shuttle_behaviour() == Sprung) {
177 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
179 style_items.push_back (RadioMenuElem (style_group, _("Wheel"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_style), Wheel)));
180 if (Config->get_shuttle_behaviour() == Wheel) {
181 static_cast<RadioMenuItem*>(&style_items.back())->set_active();
184 items.push_back (MenuElem (_("Mode"), *style_menu));
188 ShuttleControl::show_shuttle_context_menu ()
190 if (shuttle_context_menu == 0) {
191 build_shuttle_context_menu ();
194 shuttle_context_menu->popup (1, gtk_get_current_event_time());
198 ShuttleControl::set_shuttle_max_speed (float speed)
200 shuttle_max_speed = speed;
204 ShuttleControl::on_button_press_event (GdkEventButton* ev)
210 if (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), _("Stopped"));
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) {
482 ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s)
483 : PBD::Controllable (X_("Shuttle"))
489 ShuttleControl::ShuttleControllable::set_id (const std::string& str)
495 ShuttleControl::ShuttleControllable::set_value (double val)
503 fract = -((0.5 - val)/0.5);
505 fract = ((val - 0.5)/0.5);
509 sc.set_shuttle_fract (fract);
513 ShuttleControl::ShuttleControllable::get_value () const
515 return sc.get_shuttle_fract ();
519 ShuttleControl::parameter_changed (std::string p)
521 if (p == "shuttle-behaviour") {
522 switch (Config->get_shuttle_behaviour ()) {
526 if (_session->transport_rolling()) {
527 shuttle_fract = SHUTTLE_FRACT_SPEED1;
528 _session->request_transport_speed (1.0);
537 } else if (p == "shuttle-units") {