Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement partial & repeat ties #25745

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open

Conversation

miiizen
Copy link
Contributor

@miiizen miiizen commented Dec 5, 2024

Resolves: #25426

This PR introduces partial ties to MuseScore, enabling ties to be connected across repeats. When adding a tie before a repeat item (repeat barlines, markers, jumps, and voltas) if any note on the other side of the repeat destination can be tied, a partial tie will be added.
When more than one endpoint is available, clicking the outgoing tie will open a menu of possible endpoints. These can be toggled on and off.

Screenshot 2024-12-05 at 13 06 28 Screenshot 2024-12-05 at 13 07 54
Screen.Recording.2024-12-05.at.14.33.29.mov

Playback implementation to follow.

@miiizen miiizen requested review from bkunda and oktophonie December 5, 2024 14:36
@@ -1080,7 +1080,7 @@ std::set<size_t> CompatMidiRender::getNotesIndexesToRender(Chord* chord)
}

auto noteShouldBeRendered = [](Note* n) {
while (n->tieBack() && n != n->tieBack()->startNote()) {
while (n->tieBack() && !n->incomingPartialTie() && n != n->tieBack()->startNote()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I've seen this check repeated several times: every time you are looking for a tie-back-but-not-incoming-partial. Perhaps this is worth adding a tieBackNonPartial() method?

static Tie* createAndAddTie(Note* startNote, Note* endNote)
{
Score* score = startNote->score();
const bool createPartialTie = !endNote;
Copy link
Contributor

Choose a reason for hiding this comment

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

I actually find it more readable without this additional bool that's just the negation of another

Tie* tie = toTie(originalNote->tieFor()->linkedClone());
tie->setScore(score);
newNote->setTieFor(tie);
tie->setStartNote(newNote);
tie->setTrack(newNote->track());
tieMap.add(originalNote->tieFor(), tie);
}
if (originalNote->tieBack()) {
if (originalNote->tieBack() && originalNote->tieBack()->type() == ElementType::TIE) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above (kind of). Maybe we could modify tieBack and tieFor to return only "real" ties, and not LV and/or partial ties?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I considered this, but it seemed to me that there were far more scenarios where we want tieFor and tieBack to return LVs & partial ties than when we didn't.


Segment* seg = first(SegmentType::ChordRest);
while (seg) {
for (track_idx_t track = startTrack; track < ntracks; track++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since you're checking all the available tracks, I think you can more compactly do
for (EngravingItem* item : seg->elist()) {}

tieFor()->endNote()->setTieBack(tieFor());
}
}

bool Note::followingJumpItem()
Copy link
Contributor

Choose a reason for hiding this comment

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

From the name of this function I expected it to return an EngravingItem. You could either rename it into hasFollowingJumpItem, or it seems like it could also be useful to return the items themselves rather than bools?

return false;
}

String Note::precedingJumpItemName()
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this function really should not be here. At a first glance, I thought this was the symmetric of followingJumpItem but it looks like it has a completely different use and the fact that it returns a String left me quite confused.

From what I understand, the string returned by this function has a UI-related purpose which is exclusively referred to partial ties, so it definitely should be in the PartialTie class (perhaps as a static method)

void toggleEndPoint(const String& id);

void addTie(TieEndPoint* endPoint);
void removeTie(TieEndPoint* endPoint);
Copy link
Contributor

Choose a reason for hiding this comment

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

The fact that there is a public add method but also an addTie is a bit odd. Someone reading the public interface of your class wouldn't know why they are different and which one they should use. Could there be a more descriptive name?

{
Note* note = endPoint->note();
Score* score = note ? note->score() : nullptr;
Tie* _startTie = startTie();
Copy link
Contributor

Choose a reason for hiding this comment

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

m_startTie

if (std::find_if((*tieEndPoints()).begin(), (*tieEndPoints()).end(), findEndTie) != (*tieEndPoints()).end()) {
continue;
}
score()->undoRemoveElement(tie);
Copy link
Contributor

Choose a reason for hiding this comment

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

A method of the Tie class should not be removing other ties from the Score, it could be a bit dangerous. Also, from a function named collectPossibleEndPoints I'd probably expect something which returns me a list of TieEndPoint*, not something which actively modifies the current tie and even the score. Here I would try to do a combination of (not necessarily all of them)

  • Giving this method a more descriptive name
  • Moving out the code which removes other ties into a separate function
  • Given that this method performs an undoable action on the score, it looks more like the kind of methods we usually put in edit.cpp, that's also an option

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this code to remove ties is absolutely in the wrong place - it doesn't even cover all the situations I was hoping.
I think we need to remove stale endpoints when the repeat structure is recalculated - either inside or just after RepeatList::unwind.

@@ -25,6 +25,64 @@
#include "slurtie.h"

namespace mu::engraving {
class TieEndPointList;
class TieEndPoint
Copy link
Contributor

Choose a reason for hiding this comment

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

Side question: would TieJumpPoint be perhaps a better name for this class? I know what you mean by TieEndPoint, but it feels to me like it doesn't communicate the main purpose. Also because we often call "end point" the graphical end points too.

case ElementType::PARTIAL_TIE: {
PartialTie* pt = toPartialTie(e);
pt->setTick(tick());
if (pt->partialSpannerDirection() == PartialSpannerDirection::OUTGOING) {
Copy link
Contributor

Choose a reason for hiding this comment

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

pt->itOutGoing() ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement partial spanners over repeats & jumps
3 participants