Skip to content

Adapt "Split Part at Playhead" to support multiple selected parts - #2472

Open
4dTailor wants to merge 13 commits into
openutau:masterfrom
4dTailor:feature/split-multiple-parts
Open

4dTailor wants to merge 13 commits into
openutau:masterfrom
4dTailor:feature/split-multiple-parts

Conversation

@4dTailor

Copy link
Copy Markdown
Contributor

Summary

Select multiple parts, and if they are under the playhead, it will split them one by one.
Also modifies the Split Part warning to take the part name.

@4dTailor
4dTailor requested a review from a team September 28, 2026 12:09

@stakira stakira left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This looks correct and low-risk. Using the selection matches the other part actions, since right-clicking an unselected part selects it first, and the translations without {0} still work (string.Format just ignores the name).

One change I'd ask for before merging:

Make splitting several parts one undo step. Each SplitPart opens its own undo group, so splitting 5 parts takes 5 undos. Also, answering "No" in the "notes in the way" dialog for a later part leaves the earlier ones already split. Suggestion: first go through the selected parts, asking the question where needed and working out each split tick. Then do all the remove/add commands inside a single StartUndoGroup()/EndUndoGroup(). That also keeps the undo group from staying open while a dialog is waiting.

Nits:

  • The command lambda no longer uses its argument, so ReactiveCommand.Create<UPart>(async _ => await SplitParts()) would make that clear.
  • There's a whitespace-only blank line after SplitParts().
  • The other languages won't show the part name until their captions get a {0}. Not a blocker, just worth mentioning to translators.

@4dTailor

Copy link
Copy Markdown
Contributor Author

Asking a clarifying question regarding the "notes in the way" dialog, Is intended behaviour to abort the split if the user responds no, or to continue with the split, skipping the split for the part the user responded no to?

@stakira

stakira commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Asking a clarifying question regarding the "notes in the way" dialog, Is intended behaviour to abort the split if the user responds no, or to continue with the split, skipping the split for the part the user responded no to?

Either is fine for me. But again, no undo should be pushed if no part is splitted.

@4dTailor

4dTailor commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Changes have been made.
I've chosen the second option in terms of "notes in the way" behaviour - changing to the first option can be done by replacing Line 1859 and 1860 in MainWindow.axaml.cs with return;.

Extra changes:

  • Changed context.part.split to Split part/s at playhead to indicate this new behaviour
  • Sorted the selected parts by track number, so that splitting is done from top part to bottom part

@stakira

stakira commented Oct 4, 2026

Copy link
Copy Markdown
Collaborator

Thanks for this! The undo grouping looks right: all dialogs finish before StartUndoGroup, "No" for every part returns before opening a group, and splits that end up doing nothing leave an empty group, which EndUndoGroup discards.

One bug though: when the selection includes a wave part, the first loop continues before partSplitTick.Add(...), so partSplitTick only has entries for voice parts while the second loop indexes selectedParts[i], which still contains the wave parts. For example, selecting a wave part plus voice parts A and B gives ticks [tA, tB], so the wave part is "split" at tA (no-op), A is split at B's tick, and B isn't split at all.

Simplest fix is to filter to voice parts up front, then every remaining part adds exactly one entry and the indices line up:

UVoicePart[] selectedParts = viewModel.TracksViewModel.Parts
    .Where(viewModel.TracksViewModel.SelectedParts.Contains)
    .OfType<UVoicePart>()
    .OrderBy(part => part.trackNo)
    .ToArray();

(and drop the is not UVoicePart check in the loop).

Smaller things:

  • DocManager.Inst.playPosTick is read per part after each dialog; if playback is running, later parts split at later ticks. Reading it once before the loop avoids that.
  • Optional: wrapping the split loop in try/finally { EndUndoGroup(); } so an exception can't leave the undo group open.
  • The dialog title uses part.name while the log uses part.DisplayName; the latter is probably what the title should show.
  • "Split part/s at playhead" might read better as "Split parts at playhead". The new {0} in dialogs.splitpart.caption also means the translated strings need updating, or they won't show the part name.

@4dTailor

4dTailor commented Oct 4, 2026

Copy link
Copy Markdown
Contributor Author

I've done the fixes above.

Notes

Regarding the translation string
I've avoided that issue by appending the display name outside of the translation string.
I want to note that a language with RTL text could make this change look wrong.

I've also added some part selection that visually highlights the targeted part.
Original selection behaviour is restored at the split part loop.
Split-feature2

Regarding the Split part/s at playhead
I chose to rename this string that to indicate that splitting one part/many parts is possible, without adding a translation string for plurals and writing code for it. I've done the change as requested, but would like to note some minor confusion is possible regarding if the split part command still splits one part. An alternative candidate would be Split part(s) at playhead, shared by other commands with similar behaviour.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants