From 31640a9a5bb0fa637b67a564fa2fa688412908fc Mon Sep 17 00:00:00 2001 From: Colin Fletcher Date: Sun, 2 Jan 2022 22:22:45 +0000 Subject: [PATCH] gtk2_ardour: implement "Unlink from unselected" for MIDI regions An attempt to satisfy #8848. Add a new action, "fork-regions-from-unselected", which unlinks all selected MIDI regions from any unselected regions, but maintains links within the selection, and add the new action to the region MIDI context menu as "Unlink from unselected". Rename the existing "fork-region" action to "fork-selected-regions", and amend the existing "Unlink from other copies" menu item to "Unlink all selected regions" to (try to) clarify the difference. Attach the U default key-binding to the new action: I personally think it's generally slightly more useful (otherwise I wouldn't have implemented it), though I'm not that fussed. In the case that there's only one MIDI region selected, or that none of the selected regions are mutually linked, both actions will have exactly the same result. Ideally, we'd only show a single menu item in this case, but that would require (a) implementing a function to check whether the selection contains any linked regions, and (b) making the region MIDI context sub-menu dynamically generated, so that it can change based on the result of that function, neither of which I've tried to do yet. --- gtk2_ardour/ardour.keys.in | 2 +- gtk2_ardour/ardour.menus.in | 6 ++- gtk2_ardour/editor.h | 3 +- gtk2_ardour/editor_actions.cc | 3 +- gtk2_ardour/editor_ops.cc | 85 ++++++++++++++++++++++++++++++++- gtk2_ardour/editor_selection.cc | 3 +- 6 files changed, 95 insertions(+), 7 deletions(-) diff --git a/gtk2_ardour/ardour.keys.in b/gtk2_ardour/ardour.keys.in index 4cb4aabf0a..f53032e785 100644 --- a/gtk2_ardour/ardour.keys.in +++ b/gtk2_ardour/ardour.keys.in @@ -175,7 +175,7 @@ This mode provides many different operations on both regions and control points, @edit|Editor/alternate-redo| <@PRIMARY@>y|redo @select|Editor/select-all-between-cursors| <@PRIMARY@>u|select all regions enclosed by Range @select|Editor/select-all-within-cursors| u|select all regions touched by Range -@rop|Region/fork-region| <@TERTIARY@>u|unlink midi from other regions +@rop|Region/fork-regions-from-unselected| <@TERTIARY@>u|unlink midi from unselected regions @eep|Region/insert-region-from-source-list| i|insert from region list @sess|Common/addExistingAudioFiles| <@PRIMARY@>i|import audio files @gselect|Common/invert-selection| <@PRIMARY@><@TERTIARY@>i|invert selection diff --git a/gtk2_ardour/ardour.menus.in b/gtk2_ardour/ardour.menus.in index 7c56127275..11256260ed 100644 --- a/gtk2_ardour/ardour.menus.in +++ b/gtk2_ardour/ardour.menus.in @@ -357,7 +357,8 @@ - + + @@ -857,7 +858,8 @@ - + + diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 7b85928603..cf37bf50a7 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -1368,7 +1368,8 @@ private: void transpose_region (); void transpose_regions (const RegionSelection& rs); void insert_patch_change (bool from_context); - void fork_region (); + void fork_selected_regions (); + void fork_regions_from_unselected (); void do_insert_time (); void insert_time (Temporal::timepos_t const &, Temporal::timecnt_t const &, Editing::InsertTimeOption, bool, bool, bool, bool, bool, bool); diff --git a/gtk2_ardour/editor_actions.cc b/gtk2_ardour/editor_actions.cc index ab7200e876..4a0bb6f0cd 100644 --- a/gtk2_ardour/editor_actions.cc +++ b/gtk2_ardour/editor_actions.cc @@ -1910,7 +1910,8 @@ Editor::register_region_actions () register_region_action (_region_actions, RegionActionTarget (SelectedRegions|EnteredRegions), "remove-overlap", _("Remove Overlap"), sigc::bind(sigc::mem_fun (*this, &Editor::legatize_region), true)); register_region_action (_region_actions, RegionActionTarget (SelectedRegions|EnteredRegions), "insert-patch-change", _("Insert Patch Change..."), sigc::bind (sigc::mem_fun (*this, &Editor::insert_patch_change), false)); register_region_action (_region_actions, RegionActionTarget (SelectedRegions|EnteredRegions), "insert-patch-change-context", _("Insert Patch Change..."), sigc::bind (sigc::mem_fun (*this, &Editor::insert_patch_change), true)); - register_region_action (_region_actions, RegionActionTarget (SelectedRegions|EnteredRegions), "fork-region", _("Unlink from other copies"), sigc::mem_fun (*this, &Editor::fork_region)); + register_region_action (_region_actions, RegionActionTarget (SelectedRegions|EnteredRegions), "fork-selected-regions", _("Unlink all selected regions"), sigc::mem_fun (*this, &Editor::fork_selected_regions)); + register_region_action (_region_actions, RegionActionTarget (SelectedRegions|EnteredRegions), "fork-regions-from-unselected", _("Unlink from unselected"), sigc::mem_fun (*this, &Editor::fork_regions_from_unselected)); register_region_action (_region_actions, RegionActionTarget (SelectedRegions|EnteredRegions), "strip-region-silence", _("Strip Silence..."), sigc::mem_fun (*this, &Editor::strip_region_silence)); register_region_action (_region_actions, RegionActionTarget (SelectedRegions), "set-selection-from-region", _("Set Range Selection"), sigc::mem_fun (*this, &Editor::set_selection_from_region)); diff --git a/gtk2_ardour/editor_ops.cc b/gtk2_ardour/editor_ops.cc index 7f564e3297..4e1ff668f2 100644 --- a/gtk2_ardour/editor_ops.cc +++ b/gtk2_ardour/editor_ops.cc @@ -5871,8 +5871,91 @@ Editor::apply_midi_note_edit_op (MidiOperator& op, const RegionSelection& rs) } } +#include "ardour/midi_source.h" // MidiSource::name() + void -Editor::fork_region () +Editor::fork_regions_from_unselected () +{ + /* keep linkage between regions in the selection, but unlink from unselected regions */ + RegionSelection rs = get_regions_from_selection_and_entered (); + + if (rs.empty()) { + return; + } + + CursorContext::Handle cursor_ctx = CursorContext::create(*this, _cursors->wait); + bool in_command = false; + + gdk_flush (); + + /* find the set of all MidiSources associated with the selected regions */ + std::set > sources_list; + for (const auto& r : rs) { + const MidiRegionView* const mrv = dynamic_cast(r); + if (!mrv) + continue; // not a MIDI region + + sources_list.insert(mrv->midi_region()->midi_source()); + } + + std::set > affected_playlists; + for (auto r : rs) { + const MidiRegionView* const mrv = dynamic_cast(r); + if (mrv && sources_list.find(mrv->midi_region()->midi_source()) != sources_list.end()) { + affected_playlists.insert(mrv->region()->playlist()); + } + } + for (auto p : affected_playlists) { + p->clear_changes (); + p->freeze (); + } + + /* iterate over sources that need to be duplicated */ + for (const auto& ms : sources_list) { + /* duplicate source */ + boost::shared_ptr new_source = _session->create_midi_source_for_session (ms->name()); + for (RegionSelection::iterator r = rs.begin(); r != rs.end(); ) { + RegionSelection::iterator tmp = r; + ++tmp; + + const MidiRegionView* const mrv = dynamic_cast(*r); + + if (!mrv) { + r = tmp; + continue; + } + if (mrv->midi_region()->midi_source() != ms) { + r = tmp; + continue; + } + + try { + if (!in_command) { + begin_reversible_command (_("Unlink from unselected")); + in_command = true; + } + boost::shared_ptr playlist = mrv->region()->playlist(); + boost::shared_ptr new_region = mrv->midi_region()->clone (new_source); + new_region->set_name (mrv->region()->name() + "-unlinked-region"); + playlist->replace_region (mrv->region(), new_region, mrv->region()->position()); + } catch (...) { + error << string_compose (_("Could not unlink %1"), mrv->region()->name()) << endmsg; + } + r = tmp; + } + } + if (in_command) { + for (auto p : affected_playlists) { + p->thaw (); + _session->add_command(new StatefulDiffCommand (p)); + } + + commit_reversible_command (); + } +} + +void +Editor::fork_selected_regions () { RegionSelection rs = get_regions_from_selection_and_entered (); diff --git a/gtk2_ardour/editor_selection.cc b/gtk2_ardour/editor_selection.cc index 4718515405..c126cf98e2 100644 --- a/gtk2_ardour/editor_selection.cc +++ b/gtk2_ardour/editor_selection.cc @@ -1539,7 +1539,8 @@ Editor::sensitize_the_right_region_actions (bool because_canvas_crossing) _region_actions->get_action("legatize-region")->set_sensitive (false); _region_actions->get_action("remove-overlap")->set_sensitive (false); _region_actions->get_action("transform-region")->set_sensitive (false); - _region_actions->get_action("fork-region")->set_sensitive (false); + _region_actions->get_action("fork-selected-regions")->set_sensitive (false); + _region_actions->get_action("fork-regions-from-unselected")->set_sensitive (false); _region_actions->get_action("insert-patch-change-context")->set_sensitive (false); _region_actions->get_action("insert-patch-change")->set_sensitive (false); _region_actions->get_action("transpose-region")->set_sensitive (false);