diff --git a/gtk2_ardour/cuebox_ui.cc b/gtk2_ardour/cuebox_ui.cc index a9c7480eca..68a520fa4a 100644 --- a/gtk2_ardour/cuebox_ui.cc +++ b/gtk2_ardour/cuebox_ui.cc @@ -92,7 +92,7 @@ CueEntry::event_handler (GdkEvent* ev) break; case GDK_ENTER_NOTIFY: if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) { -// name_button->set_fill_color (UIConfiguration::instance ().color ("neutral:foregroundest")); + name_button->set_fill_color (UIConfiguration::instance ().color ("neutral:foregroundest")); set_fill_color (HSV (fill_color ()).lighter (0.15).color ()); } break; @@ -123,7 +123,7 @@ CueEntry::_size_allocate (ArdourCanvas::Rect const& alloc) name_text->size_allocate (ArdourCanvas::Rect (0, 0, height, height)); name_text->set_position (Duple (4. * scale, 2.5 * scale)); - name_text->clamp_width (width - height); + name_text->clamp_width (height); /* font scale may have changed. uiconfig 'embeds' the ui-scale in the font */ name_text->set_font_description (UIConfiguration::instance ().get_SmallBoldMonospaceFont ()); @@ -157,7 +157,7 @@ CueEntry::render (ArdourCanvas::Rect const& area, Cairo::RefPtr render_children (area, context); - { //Play triangle, needs to match TriggerEntry buttons exactly + if (false) { //Play triangle, needs to match TriggerEntry buttons exactly context->set_line_width (1 * scale); float margin = 4 * scale; @@ -249,7 +249,14 @@ CueBoxUI::context_menu (uint64_t idx) fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::Again)), sigc::bind (sigc::mem_fun (*this, &CueBoxUI::set_all_follow_action), FollowAction (FollowAction::Again), idx))); fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::ReverseTrigger)), sigc::bind (sigc::mem_fun (*this, &CueBoxUI::set_all_follow_action), FollowAction (FollowAction::ReverseTrigger), idx))); fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::ForwardTrigger)), sigc::bind (sigc::mem_fun (*this, &CueBoxUI::set_all_follow_action), FollowAction (FollowAction::ForwardTrigger), idx))); - fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::JumpTrigger)), sigc::bind (sigc::mem_fun (*this, &CueBoxUI::set_all_follow_action), FollowAction (FollowAction::JumpTrigger), idx))); + Menu* jump_menu = manage (new Menu); + MenuList& jitems = jump_menu->items (); + for (int i = 0; i < default_triggers_per_box; i++) { + FollowAction jump_fa = (FollowAction::JumpTrigger); + jump_fa.targets.set(i); + jitems.push_back (MenuElem (string_compose ("%1", (char)('A' + i)), sigc::bind (sigc::mem_fun (*this, &CueBoxUI::set_all_follow_action), jump_fa, idx))); + } + fitems.push_back (MenuElem (_("Jump..."), *jump_menu)); Menu* launch_menu = manage (new Menu); MenuList& litems = launch_menu->items (); diff --git a/gtk2_ardour/slot_properties_box.cc b/gtk2_ardour/slot_properties_box.cc index 6145030a98..68d26cbbcf 100644 --- a/gtk2_ardour/slot_properties_box.cc +++ b/gtk2_ardour/slot_properties_box.cc @@ -142,7 +142,15 @@ SlotPropertyTable::SlotPropertyTable () _follow_left.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::Again)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::Again), 0))); _follow_left.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::ReverseTrigger)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::ReverseTrigger), 0))); _follow_left.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::ForwardTrigger)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::ForwardTrigger), 0))); - _follow_left.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::JumpTrigger)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::JumpTrigger), 0))); + Menu* jump_menu = manage (new Menu); + MenuList& jitems = jump_menu->items (); + for (int i = 0; i < default_triggers_per_box; i++) { + FollowAction jump_fa = (FollowAction::JumpTrigger); + jump_fa.targets.set(i); + jitems.push_back (MenuElem (string_compose ("%1", (char)('A' + i)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), jump_fa, 0))); + } + //jitems.push_back (MenuElem ("Combo...", sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::JumpTrigger), 0))); + _follow_left.AddMenuElem (MenuElem (_("Jump..."), *jump_menu)); _follow_left.set_sizing_text (longest_follow); _follow_right.set_name("FollowAction"); @@ -151,7 +159,14 @@ SlotPropertyTable::SlotPropertyTable () _follow_right.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::Again)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::Again), 1))); _follow_right.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::ReverseTrigger)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::ReverseTrigger), 1))); _follow_right.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::ForwardTrigger)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::ForwardTrigger), 1))); - _follow_right.AddMenuElem (MenuElem (follow_action_to_string(FollowAction (FollowAction::JumpTrigger)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), FollowAction (FollowAction::JumpTrigger), 1))); + Menu* jump_menu_1 = manage (new Menu); + MenuList& jitems_1 = jump_menu_1->items (); + for (int i = 0; i < default_triggers_per_box; i++) { + FollowAction jump_fa = (FollowAction::JumpTrigger); + jump_fa.targets.set(i); + jitems_1.push_back (MenuElem (string_compose ("%1", (char)('A' + i)), sigc::bind (sigc::mem_fun (*this, &SlotPropertyTable::set_follow_action), jump_fa, 1))); + } + _follow_right.AddMenuElem (MenuElem (_("Jump..."), *jump_menu_1)); _follow_right.set_sizing_text (longest_follow); _launch_style_button.set_name("FollowAction"); diff --git a/gtk2_ardour/trigger_master.cc b/gtk2_ardour/trigger_master.cc index 2beda5a480..4a80ab38e6 100644 --- a/gtk2_ardour/trigger_master.cc +++ b/gtk2_ardour/trigger_master.cc @@ -346,7 +346,6 @@ TriggerMaster::context_menu () fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::Again)), sigc::bind (sigc::mem_fun (*this, &TriggerMaster::set_all_follow_action), FollowAction (FollowAction::Again)))); fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::ForwardTrigger)), sigc::bind (sigc::mem_fun (*this, &TriggerMaster::set_all_follow_action), FollowAction (FollowAction::ForwardTrigger)))); fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::ReverseTrigger)), sigc::bind (sigc::mem_fun (*this, &TriggerMaster::set_all_follow_action), FollowAction (FollowAction::ReverseTrigger)))); - fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::JumpTrigger)), sigc::bind (sigc::mem_fun (*this, &TriggerMaster::set_all_follow_action), FollowAction (FollowAction::JumpTrigger)))); Menu* launch_menu = manage (new Menu); MenuList& litems = launch_menu->items (); @@ -565,7 +564,7 @@ CueMaster::CueMaster (Item* parent) Event.connect (sigc::mem_fun (*this, &CueMaster::event_handler)); stop_shape = new ArdourCanvas::Polygon (this); - stop_shape->set_outline (true); + stop_shape->set_outline (false); stop_shape->set_fill (true); stop_shape->name = X_("stopbutton"); stop_shape->set_ignore_events (true); @@ -640,7 +639,6 @@ CueMaster::event_handler (GdkEvent* ev) break; case GDK_ENTER_NOTIFY: if (ev->crossing.detail != GDK_NOTIFY_INFERIOR) { - stop_shape->set_outline_color (UIConfiguration::instance ().color ("neutral:foreground")); stop_shape->set_fill_color (UIConfiguration::instance ().color ("neutral:foreground")); set_fill_color (HSV (fill_color ()).lighter (0.25).color ()); } @@ -668,7 +666,7 @@ CueMaster::_size_allocate (ArdourCanvas::Rect const& alloc) Rectangle::_size_allocate (alloc); const double scale = UIConfiguration::instance ().get_ui_scale (); - _poly_margin = 2. * scale; + _poly_margin = 2 * scale; const Distance width = _rect.width (); const Distance height = _rect.height (); @@ -690,8 +688,7 @@ void CueMaster::set_default_colors () { set_fill_color (HSV (UIConfiguration::instance ().color ("theme:bg")).darker (0.5).color ()); - stop_shape->set_outline_color (UIConfiguration::instance ().color ("neutral:foreground")); - stop_shape->set_fill_color (fill_color()); + stop_shape->set_fill_color (UIConfiguration::instance ().color ("neutral:midground")); } void @@ -723,8 +720,6 @@ CueMaster::context_menu () fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::Again)), sigc::bind (sigc::mem_fun (*this, &CueMaster::set_all_follow_action), FollowAction (FollowAction::Again)))); fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::ForwardTrigger)), sigc::bind (sigc::mem_fun (*this, &CueMaster::set_all_follow_action), FollowAction (FollowAction::ForwardTrigger)))); fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::ReverseTrigger)), sigc::bind (sigc::mem_fun (*this, &CueMaster::set_all_follow_action), FollowAction (FollowAction::ReverseTrigger)))); - fitems.push_back (MenuElem (TriggerUI::follow_action_to_string(FollowAction (FollowAction::JumpTrigger)), sigc::bind (sigc::mem_fun (*this, &CueMaster::set_all_follow_action), FollowAction (FollowAction::JumpTrigger)))); - Menu* launch_menu = manage (new Menu); MenuList& litems = launch_menu->items (); diff --git a/gtk2_ardour/trigger_page.cc b/gtk2_ardour/trigger_page.cc index 7489455241..704e3770ef 100644 --- a/gtk2_ardour/trigger_page.cc +++ b/gtk2_ardour/trigger_page.cc @@ -66,8 +66,8 @@ using namespace std; TriggerPage::TriggerPage () : Tabbable (_content, _("Trigger Drom"), X_("trigger")) , _cue_area_frame (0.5, 0, 1.0, 0) - , _cue_box (32, 16 * default_triggers_per_box) - , _master_widget (32, 16) + , _cue_box (16, 16 * default_triggers_per_box) + , _master_widget (16, 16) , _master (_master_widget.root ()) { load_bindings (); @@ -84,6 +84,7 @@ TriggerPage::TriggerPage () _cue_area_box.pack_start (*spacer, Gtk::PACK_SHRINK); _cue_area_box.pack_start (_cue_box, Gtk::PACK_SHRINK); _cue_area_box.pack_start (_master_widget, Gtk::PACK_SHRINK); + _cue_area_box.pack_start (_cue_rec_enable, Gtk::PACK_SHRINK); /* left-side frame, same layout as TriggerStrip. * use Alignment instead of Frame with SHADOW_IN (2px) @@ -286,6 +287,10 @@ TriggerPage::set_session (Session* s) Editor::instance ().get_selection ().TriggersChanged.connect (sigc::mem_fun (*this, &TriggerPage::selection_changed)); + TriggerBox::CueRecordingChanged.connect (_session_connections, invalidator (*this), boost::bind (&TriggerPage::rec_state_changed, this), gui_context ()); + rec_state_changed(); + _cue_rec_enable.signal_clicked.connect(sigc::mem_fun(*this, &TriggerPage::rec_state_clicked)); + initial_track_display (); _slot_prop_box.set_session (s); @@ -481,6 +486,18 @@ TriggerPage::redisplay_track_list () } } +void +TriggerPage::rec_state_clicked () +{ + TriggerBox::set_cue_recording(!TriggerBox::cue_recording()); +} + +void +TriggerPage::rec_state_changed () +{ + _cue_rec_enable.set_active_state( TriggerBox::cue_recording() ? Gtkmm2ext::ExplicitActive : Gtkmm2ext::Off); +} + void TriggerPage::parameter_changed (string const& p) { diff --git a/gtk2_ardour/trigger_page.h b/gtk2_ardour/trigger_page.h index 468220d81d..fd02c191f3 100644 --- a/gtk2_ardour/trigger_page.h +++ b/gtk2_ardour/trigger_page.h @@ -74,6 +74,9 @@ private: void pi_property_changed (PBD::PropertyChange const&); void stripable_property_changed (PBD::PropertyChange const&, boost::weak_ptr); + void rec_state_changed (); + void rec_state_clicked (); + void add_sidebar_page (std::string const&, Gtk::Widget&); bool no_strip_button_event (GdkEventButton*); @@ -111,6 +114,7 @@ private: CueBoxWidget _cue_box; FittedCanvasWidget _master_widget; CueMaster _master; + ArdourWidgets::ArdourButton _cue_rec_enable; SlotPropertiesBox _slot_prop_box; diff --git a/gtk2_ardour/trigger_ui.cc b/gtk2_ardour/trigger_ui.cc index 782864ed87..169784d86b 100644 --- a/gtk2_ardour/trigger_ui.cc +++ b/gtk2_ardour/trigger_ui.cc @@ -588,40 +588,18 @@ TriggerUI::follow_context_menu () _ignore_menu_action = true; fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::None)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::None)))); - if (trigger ()->follow_action (0) == FollowAction::None) { - dynamic_cast (&fitems.back ())->set_active (true); - } fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::Stop)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::Stop)))); - if (trigger ()->follow_action (0) == FollowAction::Stop) { - dynamic_cast (&fitems.back ())->set_active (true); - } fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::Again)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::Again)))); - if (trigger ()->follow_action (0) == FollowAction::Again) { - dynamic_cast (&fitems.back ())->set_active (true); - } fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::ForwardTrigger)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::ForwardTrigger)))); - if (trigger ()->follow_action (0) == FollowAction::ForwardTrigger) { - dynamic_cast (&fitems.back ())->set_active (true); - } fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::ReverseTrigger)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::ReverseTrigger)))); - if (trigger ()->follow_action (0) == FollowAction::ReverseTrigger) { - dynamic_cast (&fitems.back ())->set_active (true); - } - -#if 0 - fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::FirstTrigger)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::FirstTrigger)))); - if (trigger ()->follow_action (0) == FollowAction::FirstTrigger) { - dynamic_cast (&fitems.back ())->set_active (true); - } - fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::LastTrigger)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::LastTrigger)))); - if (trigger ()->follow_action (0) == FollowAction::LastTrigger) { - dynamic_cast (&fitems.back ())->set_active (true); - } -#endif - fitems.push_back (RadioMenuElem (fagroup, TriggerUI::follow_action_to_string(FollowAction (FollowAction::JumpTrigger)), sigc::bind(sigc::mem_fun (*this, &TriggerUI::set_follow_action), FollowAction (FollowAction::JumpTrigger)))); - if (trigger ()->follow_action (0) == FollowAction::JumpTrigger) { - dynamic_cast (&fitems.back ())->set_active (true); + Menu* jump_menu = manage (new Menu); + MenuList& jitems = jump_menu->items (); + for (int i = 0; i < default_triggers_per_box; i++) { + FollowAction jump_fa = (FollowAction::JumpTrigger); + jump_fa.targets.set(i); + jitems.push_back (MenuElem (string_compose ("%1", (char)('A' + i)), sigc::bind (sigc::mem_fun (*this, &TriggerUI::set_follow_action), jump_fa))); } + fitems.push_back (MenuElem (_("Jump..."), *jump_menu)); _ignore_menu_action = false; diff --git a/gtk2_ardour/triggerbox_ui.cc b/gtk2_ardour/triggerbox_ui.cc index a454fd6c70..25372cf0be 100644 --- a/gtk2_ardour/triggerbox_ui.cc +++ b/gtk2_ardour/triggerbox_ui.cc @@ -212,53 +212,60 @@ TriggerEntry::draw_follow_icon (Cairo::RefPtr context, FollowAct break; case FollowAction::ForwardTrigger: context->move_to (size / 2, 3 * scale); - context->line_to (size / 2, size - 3 * scale); + context->line_to (size / 2, size - 5 * scale); context->stroke (); - - context->arc (size / 2, 7 * scale, 2 * scale, 0, 2 * M_PI); - set_source_rgba (context, fg_color); - context->fill (); - - context->arc (size / 2, 7 * scale, 1 * scale, 0, 2 * M_PI); - set_source_rgba (context, fill_color ()); - context->fill (); - - set_source_rgba (context, fg_color); - context->arc (size / 2, size - 3 * scale, 2 * scale, 0, 2 * M_PI); // arrow head + context->arc (size / 2, size - 5 * scale, 2 * scale, 0, 2 * M_PI); // arrow head context->fill (); break; case FollowAction::ReverseTrigger: - context->arc (size / 2, 3 * scale, 2 * scale, 0, 2 * M_PI); // arrow head - set_source_rgba (context, fg_color); - context->fill (); - - context->move_to (size / 2, 3 * scale); + context->move_to (size / 2, 5 * scale); context->line_to (size / 2, size - 3 * scale); context->stroke (); - - context->arc (size / 2, size - 7 * scale, 2 * scale, 0, 2 * M_PI); - set_source_rgba (context, fg_color); + context->arc (size / 2, 5 * scale, 2 * scale, 0, 2 * M_PI); // arrow head context->fill (); - - context->arc (size / 2, size - 7 * scale, 1 * scale, 0, 2 * M_PI); - set_source_rgba (context, bg_color); - context->fill (); - break; - /* ben: new shape here ? */ case FollowAction::JumpTrigger: { - context->set_line_width (1.5 * scale); - set_source_rgba (context, HSV (UIConfiguration::instance ().color ("neutral:midground")).lighter (0.25).color ()); // needs to be brighter to maintain balance - for (int i = 0; i < 6; i++) { - Cairo::Matrix m = context->get_matrix (); - context->translate (size / 2, size / 2); - context->rotate (i * M_PI / 3); - context->move_to (0, 2 * scale); - context->line_to (0, (size / 2) - 4 * scale); - context->stroke (); - context->set_matrix (m); + if ( icon.targets.count() == 1 ) { //jump to a specific row + int cue_idx = -1; + for (int i = 0; i < default_triggers_per_box; i++) { + if (icon.targets.test(i)) { + cue_idx = i; + break; + } + } + Glib::RefPtr layout = Pango::Layout::create (context); + layout->set_font_description (UIConfiguration::instance ().get_SmallMonospaceFont ()); + layout->set_text (string_compose ("%1", (char)('A' + cue_idx))); //XXX translate? + int tw, th; + layout->get_pixel_size (tw, th); + context->move_to (size / 2, size / 2); + context->rel_move_to (-tw / 2, -th / 2); + layout->show_in_cairo_context (context); + } else if (false) { // 'ANY' jump + for (int i = 0; i < 6; i++) { + Cairo::Matrix m = context->get_matrix (); + context->translate (size / 2, size / 2); + context->rotate (i * M_PI / 3); + context->move_to (0, 0); + context->line_to (0, (size / 2) - 4 * scale); + context->stroke (); + context->set_matrix (m); + } + context->set_identity_matrix (); + } else { // 'OTHER' jump + context->set_line_width (1.5 * scale); + set_source_rgba (context, HSV (UIConfiguration::instance ().color ("neutral:midground")).lighter (0.25).color ()); // needs to be brighter to maintain balance + for (int i = 0; i < 6; i++) { + Cairo::Matrix m = context->get_matrix (); + context->translate (size / 2, size / 2); + context->rotate (i * M_PI / 3); + context->move_to (0, 2 * scale); + context->line_to (0, (size / 2) - 4 * scale); + context->stroke (); + context->set_matrix (m); + } + context->set_identity_matrix (); } - context->set_identity_matrix (); } break; case FollowAction::None: default: diff --git a/libs/ardour/ardour/location.h b/libs/ardour/ardour/location.h index 826834b208..0db7e705e2 100644 --- a/libs/ardour/ardour/location.h +++ b/libs/ardour/ardour/location.h @@ -212,6 +212,8 @@ public: bool clear_xrun_markers (); bool clear_ranges (); + void clear_cue_markers (samplepos_t start, samplepos_t end); + void ripple (timepos_t const & at, timecnt_t const & distance, bool include_locked, bool notify); XMLNode& get_state (void); diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 189a338578..256c44cde7 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -1876,6 +1876,7 @@ private: void set_track_loop (bool); bool select_playhead_priority_target (samplepos_t&); void follow_playhead_priority (); + void flush_cue_recording (); /* These are synchronous and so can only be called from within the process * cycle diff --git a/libs/ardour/ardour/triggerbox.h b/libs/ardour/ardour/triggerbox.h index b88723dc39..06a82f4a10 100644 --- a/libs/ardour/ardour/triggerbox.h +++ b/libs/ardour/ardour/triggerbox.h @@ -522,6 +522,15 @@ class LIBARDOUR_API TriggerBoxThread void delete_trigger (Trigger*); }; +struct CueRecord { + int32_t cue_number; + samplepos_t when; + + CueRecord (int32_t cn, samplepos_t t): cue_number (cn), when (t) {} + CueRecord () : cue_number (0), when (0) {} +}; + +typedef PBD::RingBuffer CueRecords; class LIBARDOUR_API TriggerBox : public Processor { @@ -529,6 +538,11 @@ class LIBARDOUR_API TriggerBox : public Processor TriggerBox (Session&, DataType dt); ~TriggerBox (); + static CueRecords cue_records; + static bool cue_recording () { return _cue_recording; } + static void set_cue_recording (bool yn); + static PBD::Signal0 CueRecordingChanged; + void run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool result_required); bool can_support_io_configuration (const ChanCount& in, ChanCount& out); bool configure_io (ChanCount in, ChanCount out); @@ -698,6 +712,7 @@ class LIBARDOUR_API TriggerBox : public Processor static void init_pool(); static std::atomic active_trigger_boxes; + static std::atomic _cue_recording; }; class TriggerReference diff --git a/libs/ardour/location.cc b/libs/ardour/location.cc index 0d1eda11b7..b73f0bf2ad 100644 --- a/libs/ardour/location.cc +++ b/libs/ardour/location.cc @@ -980,6 +980,18 @@ Locations::add (Location *loc, bool make_current) { Glib::Threads::RWLock::WriterLock lm (_lock); + + /* Do not allow multiple cue markers in the same location */ + + if (loc->is_cue_marker()) { + for (LocationList::iterator i = locations.begin(); i != locations.end(); ++i) { + if ((*i)->is_cue_marker() && (*i)->start() == loc->start()) { + locations.erase (i); + break; + } + } + } + locations.push_back (loc); if (make_current) { @@ -1610,3 +1622,54 @@ Locations::ripple (timepos_t const & at, timecnt_t const & distance, bool includ changed(); /* EMIT SIGNAL */ } } + +void +Locations::clear_cue_markers (samplepos_t start, samplepos_t end) +{ + TempoMap::SharedPtr tmap (TempoMap::use()); + Temporal::Beats sb; + Temporal::Beats eb; + bool have_beats = false; + vector r; + + + { + Glib::Threads::RWLock::WriterLock lm (_lock); + + for (LocationList::iterator i = locations.begin(); i != locations.end(); ) { + + if ((*i)->is_cue_marker()) { + Location* l (*i); + + if (l->start().time_domain() == AudioTime) { + samplepos_t when = l->start().samples(); + if (when >= start && when < end) { + i = locations.erase (i); + r.push_back (l); + continue; + } + } else { + if (!have_beats) { + sb = tmap->quarters_at (timepos_t (start)); + eb = tmap->quarters_at (timepos_t (end)); + have_beats = true; + } + + Temporal::Beats when = l->start().beats(); + if (when >= sb && when < eb) { + r.push_back (l); + i = locations.erase (i); + continue; + } + } + } + + ++i; + } + } /* end lock scope */ + + for (auto & l : r) { + removed (l); /* EMIT SIGNAL */ + delete l; + } +} diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index ee7f55ac9d..cea969e3b1 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -1653,7 +1653,7 @@ Session::first_cue_within (samplepos_t s, samplepos_t e) } void -Session::cue_marker_change (Location* loc) +Session::cue_marker_change (Location* /* ignored */) { SessionEvent* ev = new SessionEvent (SessionEvent::SyncCues, SessionEvent::Add, SessionEvent::Immediate, 0, 0.0); queue_event (ev); @@ -1671,6 +1671,12 @@ Session::maybe_find_pending_cue () int32_t ac = _pending_cue.exchange (-1); if (ac >= 0) { _active_cue.store (ac); + + if (TriggerBox::cue_recording()) { + CueRecord cr (ac, _transport_sample); + TriggerBox::cue_records.write (&cr, 1); + /* failure is acceptable, but unlikely */ + } } } diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index bc9d3b345b..d3452a2b5a 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -1355,6 +1355,11 @@ Session::non_realtime_stop (bool abort, int on_entry, bool& finished) auditioner->cancel_audition (); } + /* This must be called while _transport_sample still reflects where we stopped + */ + + flush_cue_recording (); + if (did_record) { begin_reversible_command (Operations::capture); _have_captured = true; @@ -2079,3 +2084,35 @@ Session::actual_speed() const if (_transport_fsm->transport_speed() < 0) return - _engine_speed; return 0; } + +void +Session::flush_cue_recording () +{ + if (!TriggerBox::cue_records.read_space()) { + return; + } + + CueRecord cr; + TempoMap::SharedPtr tmap (TempoMap::use()); + + _locations->clear_cue_markers (_last_roll_location, _transport_sample); + + while (TriggerBox::cue_records.read (&cr, 1) == 1) { + BBT_Time bbt = tmap->bbt_at (timepos_t (cr.when)); + bbt = bbt.round_up_to_bar (); + + timepos_t when; + + if (tmap->time_domain() == Temporal::AudioTime) { + when = timepos_t (tmap->sample_at (bbt)); + } else { + when = timepos_t (tmap->quarters_at (bbt)); + } + + Location* l = new Location (*this, when, when, std::string(), Location::Flags (Location::IsMark|Location::IsCueMarker), cr.cue_number); + _locations->add (l); + } + + /* scheduled sync of cue markers in RT thread */ + cue_marker_change (0); +} diff --git a/libs/ardour/triggerbox.cc b/libs/ardour/triggerbox.cc index 6fc2926156..0a96230292 100644 --- a/libs/ardour/triggerbox.cc +++ b/libs/ardour/triggerbox.cc @@ -2183,6 +2183,9 @@ TriggerBox::TriggerMidiMapMode TriggerBox::_midi_map_mode (TriggerBox::Sequentia int TriggerBox::_first_midi_note = 60; std::atomic TriggerBox::active_trigger_boxes (0); TriggerBoxThread* TriggerBox::worker = 0; +CueRecords TriggerBox::cue_records (256); +std::atomic TriggerBox::_cue_recording (true); +PBD::Signal0 TriggerBox::CueRecordingChanged; void TriggerBox::init () @@ -2224,6 +2227,15 @@ TriggerBox::TriggerBox (Session& s, DataType dt) Config->ParameterChanged.connect_same_thread (*this, boost::bind (&TriggerBox::parameter_changed, this, _1)); } +void +TriggerBox::set_cue_recording (bool yn) +{ + if (yn != _cue_recording) { + _cue_recording = yn; + CueRecordingChanged (); + } +} + void TriggerBox::set_region (uint32_t slot, boost::shared_ptr region) { @@ -2712,10 +2724,12 @@ TriggerBox::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_samp _sidechain->run (bufs, start_sample, end_sample, speed, nframes, true); } - int32_t cue_bang = _session.first_cue_within (start_sample, end_sample); - if (cue_bang >= 0) { - std::cerr << " CUE BANG " << cue_bang << std::endl; - _active_scene = cue_bang; + if (!_cue_recording) { + int32_t cue_bang = _session.first_cue_within (start_sample, end_sample); + if (cue_bang >= 0) { + std::cerr << " CUE BANG " << cue_bang << std::endl; + _active_scene = cue_bang; + } } /* STEP SIX: if at this point there is an active cue, make it trigger