diff --git a/gtk2_ardour/ardour_ui.h b/gtk2_ardour/ardour_ui.h index e49c92666e..dbf8859398 100644 --- a/gtk2_ardour/ardour_ui.h +++ b/gtk2_ardour/ardour_ui.h @@ -82,6 +82,7 @@ #include "video_timeline.h" #include "widgets/ardour_button.h" +#include "widgets/ardour_dropdown.h" #include "widgets/ardour_spacer.h" #include "add_route_dialog.h" @@ -484,6 +485,9 @@ private: void update_transport_clocks (samplepos_t pos); void record_state_changed (); + ArdourWidgets::ArdourDropdown _varispeed_pulldown; + void set_default_play_spd_from_menu (double spd); + std::list _midi_tracer_windows; /* Transport Control */ diff --git a/gtk2_ardour/ardour_ui2.cc b/gtk2_ardour/ardour_ui2.cc index b71bf045a5..6b9d18bdeb 100644 --- a/gtk2_ardour/ardour_ui2.cc +++ b/gtk2_ardour/ardour_ui2.cc @@ -37,6 +37,9 @@ #include #include +#include +#include + #include #include @@ -79,6 +82,7 @@ using namespace ArdourWidgets; using namespace Gtk; using namespace Glib; using namespace ARDOUR_UI_UTILS; +using namespace Menu_Helpers; void ARDOUR_UI::setup_tooltips () @@ -98,6 +102,8 @@ ARDOUR_UI::setup_tooltips () set_tip (latency_disable_button, _("Disable all Plugin Delay Compensation. This results in the shortest delay from live input to output, but any paths with delay-causing plugins will sound later than those without.")); + set_tip (_varispeed_pulldown, _("Varispeed: change the default playback and recording speed")); + synchronize_sync_source_and_video_pullup (); editor->setup_tooltips (); @@ -528,9 +534,9 @@ ARDOUR_UI::setup_transport () ssbox->set_spacing (PX_SCALE(2)); ssbox->pack_start (sync_button, false, false, 0); ssbox->pack_start (shuttle_box, true, true, 0); + ssbox->pack_start (_varispeed_pulldown, false, false, 0); ssbox->pack_start (*shuttle_box.info_button(), false, false, 0); - /* and the main table layout */ int vpadding = 1; int hpadding = 2; @@ -630,6 +636,20 @@ ARDOUR_UI::setup_transport () transport_table.attach (editor_visibility_button, TCOL, 0, 1 , FILL, SHRINK, hpadding, vpadding); ++col; +// _varispeed_pulldown.set_icon (ArdourIcon::RecButton); + _varispeed_pulldown.set_text(_("Vari")); +// _varispeed_pulldown.set_icon(record_tape_red); + _varispeed_pulldown.AddMenuElem (MenuElem (_("None"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_default_play_spd_from_menu), 1.0))); + _varispeed_pulldown.AddMenuElem (SeparatorElem()); + _varispeed_pulldown.AddMenuElem (MenuElem (_("-10 cents"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_default_play_spd_from_menu), ShuttleControl::cents_as_speed(-10, false)))); + _varispeed_pulldown.AddMenuElem (MenuElem (_("+10 cents"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_default_play_spd_from_menu), ShuttleControl::cents_as_speed(10, false)))); + _varispeed_pulldown.AddMenuElem (SeparatorElem()); + _varispeed_pulldown.AddMenuElem (MenuElem (_("-1 semitone"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_default_play_spd_from_menu), ShuttleControl::semitones_as_speed(-1, false)))); + _varispeed_pulldown.AddMenuElem (MenuElem (_("+1 semitone"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_default_play_spd_from_menu), ShuttleControl::semitones_as_speed(1, false)))); + _varispeed_pulldown.AddMenuElem (SeparatorElem()); + _varispeed_pulldown.AddMenuElem (MenuElem (_("-1 octave"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_default_play_spd_from_menu), ShuttleControl::semitones_as_speed(-12, false)))); + _varispeed_pulldown.AddMenuElem (MenuElem (_("+1 octave"), sigc::bind (sigc::mem_fun(*this, &ARDOUR_UI::set_default_play_spd_from_menu), ShuttleControl::semitones_as_speed(12, false)))); + /* initialize */ latency_switch_changed (); session_latency_updated (true); @@ -749,6 +769,12 @@ ARDOUR_UI::layered_button_clicked () } } +void +ARDOUR_UI::set_default_play_spd_from_menu (double spd) +{ + _session->set_default_play_speed(spd); +} + void ARDOUR_UI::solo_blink (bool onoff) { diff --git a/gtk2_ardour/recorder_ui.cc b/gtk2_ardour/recorder_ui.cc index 3d10ee28f2..6b08d9dfb4 100644 --- a/gtk2_ardour/recorder_ui.cc +++ b/gtk2_ardour/recorder_ui.cc @@ -23,6 +23,9 @@ #include #include +#include +#include + #include "pbd/string_convert.h" #include "ardour/audioengine.h" @@ -66,6 +69,8 @@ using namespace Gtkmm2ext; using namespace ArdourWidgets; using namespace Gtk; using namespace std; +using namespace Menu_Helpers; + #define PX_SCALE(px) std::max ((float)px, rintf ((float)px* UIConfiguration::instance ().get_ui_scale ())) @@ -231,6 +236,9 @@ RecorderUI::RecorderUI () _button_table.attach (_btn_new_plist_rec, col, col + 2, 1, 2 , FILL, SHRINK, hpadding, vpadding); col += 2; + _button_table.attach (*(manage (new ArdourVSpacer ())), col, col + 1, 0, 2, FILL, FILL, spacepad, vpadding); + col += 1; + _toolbar.pack_start (_button_table, false, false); _toolbar.pack_end (_btn_peak_reset, false, false, 4); _toolbar.pack_end (_remain_info_box, false, false, 4); diff --git a/gtk2_ardour/recorder_ui.h b/gtk2_ardour/recorder_ui.h index 345e1c3cfd..b597ffc4f2 100644 --- a/gtk2_ardour/recorder_ui.h +++ b/gtk2_ardour/recorder_ui.h @@ -46,6 +46,7 @@ #include "input_port_monitor.h" #include "rec_info_box.h" +#include "shuttle_control.h" #include "transport_control_ui.h" namespace ARDOUR { diff --git a/gtk2_ardour/shuttle_control.cc b/gtk2_ardour/shuttle_control.cc index d660ef3ae2..3c5e44defc 100644 --- a/gtk2_ardour/shuttle_control.cc +++ b/gtk2_ardour/shuttle_control.cc @@ -44,6 +44,7 @@ #include "actions.h" #include "rgb_macros.h" #include "shuttle_control.h" +#include "timers.h" #include "pbd/i18n.h" @@ -59,17 +60,90 @@ gboolean qt (gboolean, gint, gint, gboolean, Gtk::Tooltip*, gpointer) return FALSE; } +ShuttleInfoButton::~ShuttleInfoButton () +{ + delete disp_context_menu; +} + +ShuttleInfoButton::ShuttleInfoButton () +{ + set_layout_font (UIConfiguration::instance().get_NormalFont()); + set_sizing_text (S_("LogestShuttle|+00 st")); + set_tooltip (*this, _("Speed Display (Context-click for options)")); + set_name ("shuttle text"); + set_visual_state (Gtkmm2ext::NoVisualState); + set_elements (ArdourButton::Text); + + Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, boost::bind (&ShuttleInfoButton::parameter_changed, this, _1), gui_context()); + + 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); + + disp_context_menu = 0; + _ignore_change = false; +} + +void +ShuttleInfoButton::set_shuttle_units (ShuttleUnits s) +{ + if (_ignore_change) { + return; + } + Config->set_shuttle_units (s); +} + +void +ShuttleInfoButton::parameter_changed (std::string p) +{ + /* units changed; recreate the menu when it is next opened to show the current selection*/ + if (p == "shuttle-units") { + delete disp_context_menu; + disp_context_menu = 0; + } +} + +bool +ShuttleInfoButton::on_button_press_event (GdkEventButton* ev) +{ + if (Keyboard::is_context_menu_event (ev)) { + if (disp_context_menu == 0) { + build_disp_context_menu (); + } + disp_context_menu->popup (ev->button, ev->time); + return true; + } + + return true; +} + + +void +ShuttleInfoButton::build_disp_context_menu () +{ + PBD::Unwinder uw (_ignore_change, true); + + using namespace Menu_Helpers; + + disp_context_menu = new Gtk::Menu(); + MenuList& items = disp_context_menu->items(); + + RadioMenuItem::Group units_group; + + items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleInfoButton::set_shuttle_units), Percentage))); + if (Config->get_shuttle_units() == Percentage) { + static_cast(&items.back())->set_active(); + } + items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleInfoButton::set_shuttle_units), Semitones))); + if (Config->get_shuttle_units() == Semitones) { + static_cast(&items.back())->set_active(); + } +} + + + ShuttleControl::ShuttleControl () : _controllable (new ShuttleControllable (*this)) , binding_proxy (_controllable) { - _info_button.set_layout_font (UIConfiguration::instance().get_NormalFont()); - _info_button.set_sizing_text (S_("LogestShuttle|< +00 st")); - _info_button.set_name ("shuttle text"); - _info_button.set_sensitive (false); - _info_button.set_visual_state (Gtkmm2ext::NoVisualState); - _info_button.set_elements (ArdourButton::Text); - set_tooltip (*this, _("Shuttle speed control (Context-click for options)")); pattern = 0; @@ -101,6 +175,7 @@ ShuttleControl::ShuttleControl () Config->ParameterChanged.connect (parameter_connection, MISSING_INVALIDATOR, boost::bind (&ShuttleControl::parameter_changed, this, _1), gui_context()); UIConfiguration::instance().ColorsChanged.connect (sigc::mem_fun (*this, &ShuttleControl::set_colors)); + Timers::blink_connect (sigc::mem_fun (*this, &ShuttleControl::do_blink)); /* gtkmm 2.4: the C++ wrapper doesn't work */ g_signal_connect ((GObject*) gobj(), "query-tooltip", G_CALLBACK (qt), NULL); @@ -114,6 +189,17 @@ ShuttleControl::~ShuttleControl () delete shuttle_context_menu; } +void +ShuttleControl::do_blink (bool onoff) +{ + if (!shuttle_grabbed && _session && _session->default_play_speed()!=1.0) { + _info_button.set_active(onoff); + } else { + _info_button.set_active(false); + } +} + + void ShuttleControl::set_session (Session *s) { @@ -122,6 +208,7 @@ ShuttleControl::set_session (Session *s) if (_session) { set_sensitive (true); _session->add_controllable (_controllable); + _info_button.set_session (s); } else { set_sensitive (false); } @@ -158,14 +245,62 @@ ShuttleControl::on_size_allocate (Gtk::Allocation& alloc) void ShuttleControl::map_transport_state () { - float speed; + float speed = 0.0; + float actual_speed = 0.0; + char buf[32]; - if (!_session) { - speed = 0.0; - } else { + if (_session) { speed = _session->actual_speed (); + actual_speed = speed; + if (shuttle_grabbed) { + speed = requested_speed; + } else if (_session->default_play_speed()!=1.0) { + /*use has selected a varispeed AND the shuttle widget is not held ... show the varispeed*/ + actual_speed = _session->default_play_speed(); + } } + if (actual_speed != 0) { + if (_session->default_play_speed()==1.0) { + if (actual_speed == _session->default_play_speed()) { + snprintf (buf, sizeof (buf), "%s", _("Play")); + } else if (Config->get_shuttle_units() == Percentage) { + { + if (actual_speed < 0.0) { + snprintf (buf, sizeof (buf), "< %.1f%%", -actual_speed * 100.f); + } else { + snprintf (buf, sizeof (buf), "> %.1f%%", actual_speed * 100.f); + } + } + } else { + bool reversed; + int semi = speed_as_semitones (actual_speed, reversed); + if (reversed) { + snprintf (buf, sizeof (buf), _("< %+2d st"), semi); + } else { + snprintf (buf, sizeof (buf), _("> %+2d st"), semi); + } + } + } else { /*default_play_speed (varispeed) is always forward */ + if (Config->get_shuttle_units() == Percentage) { + snprintf (buf, sizeof (buf), "%.1f%%", actual_speed * 100.f); + } else { + bool reversed; + int semi = speed_as_semitones (actual_speed, reversed); + if (abs(semi)<1) { + int cents = speed_as_cents (actual_speed, reversed); + snprintf (buf, sizeof (buf), _("%+2d c"), cents); + } else { + snprintf (buf, sizeof (buf), _("%+2d st"), semi); + } + } + } + } else { + snprintf (buf, sizeof (buf), "%s", _("Stop")); + } + _info_button.set_text(buf); + + if ( (fabsf( speed - last_speed_displayed) < 0.005f) // dead-zone && !( speed == 1.f && last_speed_displayed != 1.f) && !( speed == 0.f && last_speed_displayed != 0.f) @@ -201,21 +336,7 @@ ShuttleControl::build_shuttle_context_menu () shuttle_context_menu = new Menu(); MenuList& items = shuttle_context_menu->items(); - Menu* units_menu = manage (new Menu); - MenuList& units_items = units_menu->items(); - RadioMenuItem::Group units_group; - - units_items.push_back (RadioMenuElem (units_group, _("Percent"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Percentage))); - if (Config->get_shuttle_units() == Percentage) { - static_cast(&units_items.back())->set_active(); - } - units_items.push_back (RadioMenuElem (units_group, _("Semitones"), sigc::bind (sigc::mem_fun (*this, &ShuttleControl::set_shuttle_units), Semitones))); - if (Config->get_shuttle_units() == Semitones) { - static_cast(&units_items.back())->set_active(); - } - items.push_back (MenuElem (_("Units"), *units_menu)); - - if (Config->get_shuttle_units() == Percentage) { + { RadioMenuItem::Group speed_group; /* XXX this code assumes that Config->get_max_transport_speed() returns 8 */ @@ -249,19 +370,6 @@ ShuttleControl::build_shuttle_context_menu () items.push_back (MenuElem (_("Maximum speed"), *speed_menu)); } - - items.push_back (SeparatorElem ()); - items.push_back (MenuElem (_("Reset to 100%"), sigc::mem_fun (*this, &ShuttleControl::reset_speed))); -} - -void -ShuttleControl::reset_speed () -{ - if (!_session) { - return; - } - - _session->reset_transport_speed (); } void @@ -356,63 +464,6 @@ ShuttleControl::on_query_tooltip (int, int, bool, const Glib::RefPtrget_shuttle_units() == Semitones); - - switch (ev->direction) { - case GDK_SCROLL_UP: - case GDK_SCROLL_RIGHT: - if (semis) { - if (shuttle_fract == 0) { - shuttle_fract = semitones_as_fract (-24, false); - } else { - bool rev; - int st = fract_as_semitones (shuttle_fract, rev); - st += (rev ? -1 : 1); - if (st < -24) { - st = -24; - rev = !rev; - } - shuttle_fract = semitones_as_fract (st, rev); - } - } else { - shuttle_fract += 0.00125; - } - break; - case GDK_SCROLL_DOWN: - case GDK_SCROLL_LEFT: - if (semis) { - if (shuttle_fract == 0) { - shuttle_fract = semitones_as_fract (-24, true); - } else { - bool rev; - int st = fract_as_semitones (shuttle_fract, rev); - st += (rev ? 1 : -1); - if (st < -24) { - st = -24; - rev = !rev; - } - shuttle_fract = semitones_as_fract (st, rev); - } - } else { - shuttle_fract -= 0.00125; - } - break; - default: - return false; - } - - use_shuttle_fract (true); - - return true; -} - bool ShuttleControl::on_motion_notify_event (GdkEventMotion* ev) { @@ -466,6 +517,30 @@ ShuttleControl::speed_as_semitones (float speed, bool& reverse) } } +int +ShuttleControl::speed_as_cents (float speed, bool& reverse) +{ + assert (speed != 0.0); + + if (speed < 0.0) { + reverse = true; + return (int) ceilf (1200.0 * fast_log2 (-speed)); + } else { + reverse = false; + return (int) ceilf (1200.0 * fast_log2 (speed)); + } +} + +float +ShuttleControl::cents_as_speed (int cents, bool reverse) +{ + if (reverse) { + return -pow (2.0, (cents / 1200.0)); + } else { + return pow (2.0, (cents / 1200.0)); + } +} + float ShuttleControl::semitones_as_speed (int semi, bool reverse) { @@ -519,6 +594,7 @@ ShuttleControl::use_shuttle_fract (bool force, bool zero_ok) speed = 0.0; } } else { + shuttle_fract = shuttle_fract*shuttle_fract*shuttle_fract; // ^3 preserves the sign; speed = shuttle_max_speed * shuttle_fract; } @@ -571,12 +647,8 @@ ShuttleControl::render (Cairo::RefPtr const& ctx, cairo_rectangl cairo_stroke (cr); float speed = 0.0; - float actual_speed = 0.0; - char buf[32]; - if (_session) { speed = _session->actual_speed (); - actual_speed = speed; if (shuttle_grabbed) { speed = requested_speed; } @@ -604,34 +676,8 @@ ShuttleControl::render (Cairo::RefPtr const& ctx, cairo_rectangl } cairo_fill(cr); - /* text */ - if (actual_speed != 0) { - if (Config->get_shuttle_units() == Percentage) { - if (actual_speed == 1.0) { - snprintf (buf, sizeof (buf), "%s", _("Play")); - } else { - if (actual_speed < 0.0) { - snprintf (buf, sizeof (buf), "< %.1f%%", -actual_speed * 100.f); - } else { - snprintf (buf, sizeof (buf), "> %.1f%%", actual_speed * 100.f); - } - } - } else { - bool reversed; - int semi = speed_as_semitones (actual_speed, reversed); - if (reversed) { - snprintf (buf, sizeof (buf), _("< %+2d st"), semi); - } else { - snprintf (buf, sizeof (buf), _("> %+2d st"), semi); - } - } - } else { - snprintf (buf, sizeof (buf), "%s", _("Stop")); - } - last_speed_displayed = actual_speed; - - _info_button.set_text (buf); + last_speed_displayed = speed; #if 0 if (UIConfiguration::instance().get_widget_prelight()) { @@ -644,15 +690,6 @@ ShuttleControl::render (Cairo::RefPtr const& ctx, cairo_rectangl #endif } -void -ShuttleControl::set_shuttle_units (ShuttleUnits s) -{ - if (_ignore_change) { - return; - } - Config->set_shuttle_units (s); -} - ShuttleControl::ShuttleControllable::ShuttleControllable (ShuttleControl& s) : PBD::Controllable (X_("Shuttle")) , sc (s) @@ -674,17 +711,15 @@ ShuttleControl::ShuttleControllable::get_value () const void ShuttleControl::parameter_changed (std::string p) { - if (p == "shuttle-max-speed") { + if (p == "shuttle-units") { + map_transport_state (); + } else if (p == "shuttle-max-speed") { shuttle_max_speed = Config->get_shuttle_max_speed (); last_speed_displayed = -99999999; map_transport_state (); use_shuttle_fract (true); - } else if (p == "shuttle-units") { - last_speed_displayed = -99999999; - map_transport_state (); - use_shuttle_fract (true); delete shuttle_context_menu; - shuttle_context_menu = 0; + shuttle_context_menu=0; } } diff --git a/gtk2_ardour/shuttle_control.h b/gtk2_ardour/shuttle_control.h index c37ff8c3f4..755f0d1464 100644 --- a/gtk2_ardour/shuttle_control.h +++ b/gtk2_ardour/shuttle_control.h @@ -36,6 +36,25 @@ namespace Gtk { class Menu; } +class ShuttleInfoButton : public ArdourWidgets::ArdourButton, public ARDOUR::SessionHandlePtr +{ +public: + ShuttleInfoButton (); + ~ShuttleInfoButton (); + + bool on_button_press_event (GdkEventButton*); + + void set_shuttle_units (ARDOUR::ShuttleUnits s); + +private: + void parameter_changed (std::string); + void build_disp_context_menu (); + Gtk::Menu* disp_context_menu; + PBD::ScopedConnection parameter_connection; + + bool _ignore_change; +}; + class ShuttleControl : public CairoWidget, public ARDOUR::SessionHandlePtr { public: @@ -47,6 +66,8 @@ public: double get_shuttle_fract () const { return shuttle_fract; } void set_session (ARDOUR::Session*); + void do_blink (bool); + struct ShuttleControllable : public PBD::Controllable { ShuttleControllable (ShuttleControl&); void set_value (double, PBD::Controllable::GroupControlDisposition group_override); @@ -63,6 +84,17 @@ public: ArdourWidgets::ArdourButton* info_button () { return &_info_button; } +public: + static int speed_as_semitones (float, bool&); + static int fract_as_semitones (float, bool&); + + static float semitones_as_speed (int, bool); + static float semitones_as_fract (int, bool); + + static int speed_as_cents (float, bool&); + static float cents_as_speed (int, bool); + + protected: bool _hovering; float shuttle_max_speed; @@ -76,21 +108,21 @@ protected: cairo_pattern_t* shine_pattern; ARDOUR::microseconds_t last_shuttle_request; PBD::ScopedConnection parameter_connection; - ArdourWidgets::ArdourButton _info_button; + ShuttleInfoButton _info_button; Gtk::Menu* shuttle_context_menu; ArdourWidgets::BindingProxy binding_proxy; float bg_r, bg_g, bg_b; void build_shuttle_context_menu (); void set_shuttle_max_speed (float); - void reset_speed (); bool on_enter_notify_event (GdkEventCrossing*); bool on_leave_notify_event (GdkEventCrossing*); bool on_button_press_event (GdkEventButton*); bool on_button_release_event(GdkEventButton*); - bool on_scroll_event (GdkEventScroll*); bool on_motion_notify_event(GdkEventMotion*); + bool on_button_press_event_for_display (GdkEventButton*); + void render (Cairo::RefPtr const&, cairo_rectangle_t*); void on_size_allocate (Gtk::Allocation&); @@ -102,12 +134,6 @@ protected: void set_shuttle_units (ARDOUR::ShuttleUnits); - int speed_as_semitones (float, bool&); - int fract_as_semitones (float, bool&); - - float semitones_as_speed (int, bool); - float semitones_as_fract (int, bool); - bool _ignore_change; };