Skip to content

Prevent consolidateCompletedTuplets() from breaking complete tuplets#1858

Merged
mscuthbert merged 2 commits intocuthbertLab:masterfrom
jacobtylerwalls:jtw/tuplet-follow-ups
Apr 27, 2026
Merged

Prevent consolidateCompletedTuplets() from breaking complete tuplets#1858
mscuthbert merged 2 commits intocuthbertLab:masterfrom
jacobtylerwalls:jtw/tuplet-follow-ups

Conversation

@jacobtylerwalls
Copy link
Copy Markdown
Member

@jacobtylerwalls jacobtylerwalls commented Jan 26, 2026

Closes #1780

Before
consolidateCompletedTuplets() was violating the Hippocractic Oath of tuplet doctors: first, break no well-formed tuplets.

In more detail:

While scanning this stream...

s = converter.parse('tinyNotation: 2/4 trip{c8 d8 e8} trip{e8 e8 r8}')

the region spanning 1.0 QL from {e8 - e8 - e8} was replaced with a single e4, even though it broke the tuplets it spanned.

The reason was that the algorithm scanned the first e8, determined that it didn't match the pitch of the immediately prior d8, and reset all of the search state.

After
Now, search state is maintained regardless of whether a note is reexpressible or whether it matches the prior note. This way, search state is always cleared when a tuplet is completed, ensuring no candidates for consolidation are identified inside other well-formed tuplets.

@jacobtylerwalls jacobtylerwalls changed the title Jtw/tuplet follow ups Prevent consolidateCompletedTuplets() from breaking tuplets Jan 26, 2026
@jacobtylerwalls jacobtylerwalls changed the title Prevent consolidateCompletedTuplets() from breaking tuplets Prevent consolidateCompletedTuplets() from breaking well-formed tuplets Jan 26, 2026
@coveralls
Copy link
Copy Markdown

coveralls commented Jan 26, 2026

Coverage Status

coverage: 93.056% (+0.002%) from 93.054% — jacobtylerwalls:jtw/tuplet-follow-ups into cuthbertLab:master

@jacobtylerwalls jacobtylerwalls changed the title Prevent consolidateCompletedTuplets() from breaking well-formed tuplets Prevent consolidateCompletedTuplets() from breaking complete tuplets Jan 26, 2026
Comment thread music21/duration.py
# else: pass


class TupletSearchState: # pylint: disable=W0201
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Happy to move this to another module if this is too exposed (or too confusing with TupletFixer).

The location in the duration module explains why you see stringified annotations or stringified gEBC() calls, because this module can't import note. So that would be ripe for a polish if there's a better location.

@jacobtylerwalls jacobtylerwalls marked this pull request as ready for review January 26, 2026 00:48
@mscuthbert
Copy link
Copy Markdown
Member

Finally had a chance to read more closely and review -- generally when this much code is removed, something is lost. That's not the case here. Thank you @jacobtylerwalls ! :-)

@mscuthbert mscuthbert merged commit 210bc0f into cuthbertLab:master Apr 27, 2026
8 checks passed
@jacobtylerwalls jacobtylerwalls deleted the jtw/tuplet-follow-ups branch April 28, 2026 00:29
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.

consolidateCompletedTuplets combines rests across different triplets that causes malformed MusicXML output

3 participants