View Issue Details

IDProjectCategoryView StatusLast Update
0008848ardourfeaturespublic2022-08-18 15:28
Reportercolinf Assigned Topaul  
PrioritynormalSeverityminorReproducibilityN/A
Status resolvedResolutionfixed 
PlatformDebian GNUOSLinuxOS Version(any)
Summary0008848: Improve MIDI region unlinking behaviour
DescriptionPresently, there's no way to unlink multiple linked regions but have them remain linked to each other.

It might be useful in some cases, when unlinking multiple selected regions all at once, to preserve the links within the region selection rather than creating a new source for every region.

E.g.: if we have a set of linked MIDI regions A, A, A, A, it could be useful to be able to select (say) the last two and unlink them, and end up with A, A, A', A', where the regions A' are still linked.
TagsNo tags attached.

Activities

colinf

2022-07-11 21:36

updater   ~0026500

Path attached: plenty of room for improvement, but it Works for Me.
0001-gtk2_ardour-implement-Unlink-from-unselected-for-MID.patch (10,231 bytes)   
From 11eac4b79d3b5baad7c26d847b11df72caa5031c Mon Sep 17 00:00:00 2001
From: Colin Fletcher <colin.m.fletcher@googlemail.com>
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 <Tertiary>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 @@
         <menuitem action='legatize-region'/>
         <menuitem action='remove-overlap'/>
         <menuitem action='transform-region'/>
-        <menuitem action='fork-region'/>
+        <menuitem action='fork-selected-regions'/>
+        <menuitem action='fork-regions-from-unselected'/>
         <menuitem action='deinterlace-midi'/>
         <menuitem action='show-region-list-editor'/>
       </menu>
@@ -857,7 +858,8 @@
       <menuitem action='legatize-region'/>
       <menuitem action='remove-overlap'/>
       <menuitem action='transform-region'/>
-      <menuitem action='fork-region'/>
+      <menuitem action='fork-selected-regions'/>
+      <menuitem action='fork-regions-from-unselected'/>
       <menuitem action='deinterlace-midi'/>
       <menuitem action='show-region-list-editor'/>
     </menu>
diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h
index 229977292d..8ee7d21679 100644
--- a/gtk2_ardour/editor.h
+++ b/gtk2_ardour/editor.h
@@ -1366,7 +1366,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 b8ce9cd073..d6d1ed3041 100644
--- a/gtk2_ardour/editor_actions.cc
+++ b/gtk2_ardour/editor_actions.cc
@@ -1908,7 +1908,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 6b7ea0197c..4c81c88c24 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_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<boost::shared_ptr<MidiSource> > sources_list;
+	for (const auto& r : rs) {
+		const MidiRegionView* const mrv = dynamic_cast<const MidiRegionView*>(r);
+		if (!mrv)
+			continue; // not a MIDI region
+
+		sources_list.insert(mrv->midi_region()->midi_source());
+	}
+
+	std::set<boost::shared_ptr<Playlist> > affected_playlists;
+	for (auto r : rs) {
+		const MidiRegionView* const mrv = dynamic_cast<const MidiRegionView*>(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<MidiSource> 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<const MidiRegionView*>(*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> playlist = mrv->region()->playlist();
+				boost::shared_ptr<Region> 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_region ()
+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 63a398d5dd..7a579b532a 100644
--- a/gtk2_ardour/editor_selection.cc
+++ b/gtk2_ardour/editor_selection.cc
@@ -1543,7 +1543,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);
-- 
2.30.2

paul

2022-08-18 15:28

administrator   ~0026569

applied and pushed, thanks!

Issue History

Date Modified Username Field Change
2022-01-02 20:02 colinf New Issue
2022-07-11 21:36 colinf Note Added: 0026500
2022-07-11 21:36 colinf File Added: 0001-gtk2_ardour-implement-Unlink-from-unselected-for-MID.patch
2022-08-18 15:28 paul Assigned To => paul
2022-08-18 15:28 paul Status new => resolved
2022-08-18 15:28 paul Resolution open => fixed
2022-08-18 15:28 paul Note Added: 0026569