Skip to content

feat(api): support lv (let-ring) ties#286

Merged
webern merged 4 commits into
mainfrom
lv-ties
Jul 2, 2026
Merged

feat(api): support lv (let-ring) ties#286
webern merged 4 commits into
mainfrom
lv-ties

Conversation

@webern

@webern webern commented Jul 1, 2026

Copy link
Copy Markdown
Owner

Summary

Adds support for laissez-vibrer (lv, or "let-ring") ties to mx::api.

An lv tie applies to a single note and has no matching stop, so it does not fit the existing
start/stop tie model. Rather than overloading NoteData::isTieStart / isTieStop, it is modeled
with its own start-only structure, api::TieLetRing (the shared visual attributes: PositionData,
CurveOrientation, and optional ColorData), held in NoteData::tieLetRing as a
std::optional<TieLetRing>. In MusicXML this is a lone <tied type="let-ring"> notation; the
sound-level <tie> element has no let-ring type.

  • Reader: parseCurve in CurveFunctions.h routes a lone <tied type="let-ring">
    (core::TiedType::letRing) into NoteData::tieLetRing. An if constexpr guard keeps the branch
    out of the slur instantiation, whose type has no let-ring alternative.
  • Writer: NotationsWriter emits a <tied type="let-ring"> from the field, carrying position,
    orientation, and color.

Testing

  • Local build and tests were NOT run by the author. Relying on CI.
  • Added unit tests in CurveFunctionsTest.cpp: parseTieLetRing_* (position, placement, color,
    orientation), parseCurve_letRing_routesToTieLetRing, parseCurve_start_doesNotSetTieLetRing,
    writeAttributesFromTieLetRing_*, and tieLetRing_roundTrip.

References

Add api::TieLetRing, a start-only structure on NoteData for
laissez-vibrer ties, kept separate from the isTieStart/isTieStop
pair. Parse a lone <tied type="let-ring"> in parseCurve and emit
it in NotationsWriter, with reader/writer/round-trip unit tests.
@webern webern added feature new feature request area/mx::api area/mx::impl non-breaking fixes or implementation that do not require breaking changes ai Issues opened by, or through, a coding agent. labels Jul 1, 2026
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Coverage report

Core-dev coverage src/private/mx/core/

Metric Coverage Covered / Total
Lines 77.8% 28487 / 36624
Functions 74.3% 6349 / 8550
Branches 50.6% 22632 / 44725

API coverage src/private/mx/{api,impl,utility}/

Metric Coverage Covered / Total
Lines 78.4% 6046 / 7709
Functions 63.9% 2051 / 3211
Branches 47.6% 5120 / 10757

Core HTML report | API HTML report

Commit b5b8a034c5b81e6c75732f4e54e2c33ff80e66a1.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

gen-quality gen/

gen-quality: 84.5 / 100   (floor 84.5, +0.0)

  structure     86.5  x0.50   [fn 90.5 / file 82.6]
  cyclomatic    88.4  x0.25
  cognitive     76.6  x0.25

  409 functions across 31 files, 7702 lines (largest file 1044)
  max cc 56  max cognitive 44  max fn loc 152

Worst offenders (top 5 per axis; full lists in score.json):
  cyclomatic gen/xsd/analyze.py:311     report                             56
  cyclomatic gen/plates/build.py:956    _validate_config_against_ir        35
  cyclomatic gen/press/context.py:145   plate_context                      34
  cyclomatic gen/__main__.py:46         _ir                                23
  cyclomatic gen/tests/test_ir.py:102   _check_references                  20
  cognitive  gen/xsd/analyze.py:311     report                             44
  cognitive  gen/ir/resolve.py:119      flat_elements                      40
  cognitive  gen/tests/test_ir.py:102   _check_references                  38
  cognitive  gen/press/context.py:145   plate_context                      37
  cognitive  gen/xsd/analyze.py:207     _sccs                              37
  size       gen/xsd/analyze.py:311     report                             152
  size       gen/press/context.py:145   plate_context                      96
  size       gen/plates/build.py:533    _value_plate                       89
  size       gen/plates/build.py:956    _validate_config_against_ir        89
  size       gen/ir/resolve.py:119      flat_elements                      78

Commit b5b8a034c5b81e6c75732f4e54e2c33ff80e66a1.

@webern webern left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Not mergeable as-is.

Comment thread src/include/mx/api/NoteData.h Outdated
// rather than by overloading NoteData's isTieStart / isTieStop.
struct TieLetRing
{
// Whether this note carries an lv (let-ring) tie. When false the remaining

@webern webern Jul 1, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

This doesn't quite make sense to me. We might need this, not so sure, but if we do maybe isLvSpecified. It seems like this would be cleaner without this boot at all.

The weirder part is why this would govern all the other attributes.

Comment thread src/include/mx/api/NoteData.h Outdated

// A laissez-vibrer / let-ring tie on this note (a lone <tied type="let-ring">
// with no matching stop). Independent of isTieStart / isTieStop. See TieLetRing.
TieLetRing tieLetRing;

@webern webern Jul 1, 2026

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Oh. I see why we have that bool above. Hmm. This is dumb. I think we need to either use std::optional for the first time or consider the IsSpecified/Optional class I opened an issue about. #249

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In my experience, std::optional is a great (arguably the best) solution to this kind of problem for any type except an enum or a bool. For those types, std::optional is codesmell. Instead of optional, enums should provide an unspecified option (which should be the {} initialization default) and bools should be converted to 3-state enums (which this repo already does).

…LetRing>

Per review feedback on #286: the isSpecified bool inside TieLetRing is
awkward because presence of the lv tie should be expressed at the field
level, not inside the struct. Change NoteData::tieLetRing from TieLetRing
to std::optional<TieLetRing> and remove the now-redundant isSpecified
member. Update the writer (has_value()), reader (assign directly), and
tests (has_value() / remove parseTieLetRing_isSpecified test).
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Coverage report

Core-dev coverage src/private/mx/core/

Metric Coverage Covered / Total
Lines 77.8% 28487 / 36624
Functions 74.3% 6349 / 8550
Branches 50.6% 22632 / 44725

API coverage src/private/mx/{api,impl,utility}/

Metric Coverage Covered / Total
Lines 78.4% 6050 / 7713
Functions 63.9% 2051 / 3211
Branches 47.6% 5123 / 10761

Core HTML report | API HTML report

Commit 70107808535867d266ba7e6cd879edb75a5314d5.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

gen-quality gen/

(no report produced)

Commit 70107808535867d266ba7e6cd879edb75a5314d5.

CI failed fmt-check after the previous commit; this applies clang-format's
line-collapse for the now-shorter TieLetRing() initializer list.
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Coverage report

Core-dev coverage src/private/mx/core/

Metric Coverage Covered / Total
Lines 77.8% 28487 / 36624
Functions 74.3% 6349 / 8550
Branches 50.6% 22632 / 44725

API coverage src/private/mx/{api,impl,utility}/

Metric Coverage Covered / Total
Lines 78.4% 6050 / 7713
Functions 63.9% 2051 / 3211
Branches 47.6% 5123 / 10761

Core HTML report | API HTML report

Commit 065eb6b3aba2f1549be99057edf604e7d01f8df8.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

gen-quality gen/

gen-quality: 84.5 / 100   (floor 84.5, +0.0)

  structure     86.5  x0.50   [fn 90.5 / file 82.6]
  cyclomatic    88.4  x0.25
  cognitive     76.6  x0.25

  409 functions across 31 files, 7702 lines (largest file 1044)
  max cc 56  max cognitive 44  max fn loc 152

Worst offenders (top 5 per axis; full lists in score.json):
  cyclomatic gen/xsd/analyze.py:311     report                             56
  cyclomatic gen/plates/build.py:956    _validate_config_against_ir        35
  cyclomatic gen/press/context.py:145   plate_context                      34
  cyclomatic gen/__main__.py:46         _ir                                23
  cyclomatic gen/tests/test_ir.py:102   _check_references                  20
  cognitive  gen/xsd/analyze.py:311     report                             44
  cognitive  gen/ir/resolve.py:119      flat_elements                      40
  cognitive  gen/tests/test_ir.py:102   _check_references                  38
  cognitive  gen/press/context.py:145   plate_context                      37
  cognitive  gen/xsd/analyze.py:207     _sccs                              37
  size       gen/xsd/analyze.py:311     report                             152
  size       gen/press/context.py:145   plate_context                      96
  size       gen/plates/build.py:533    _value_plate                       89
  size       gen/plates/build.py:956    _validate_config_against_ir        89
  size       gen/ir/resolve.py:119      flat_elements                      78

Commit 065eb6b3aba2f1549be99057edf604e7d01f8df8.

Trim the musical explanation of what an lv/let-ring tie is (musicians
already know) and keep only the non-obvious parts: the MusicXML mapping
and why it's a separate struct instead of overloading isTieStart/isTieStop.
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

Coverage report

Core-dev coverage src/private/mx/core/

Metric Coverage Covered / Total
Lines 77.8% 28487 / 36624
Functions 74.3% 6349 / 8550
Branches 50.6% 22632 / 44725

API coverage src/private/mx/{api,impl,utility}/

Metric Coverage Covered / Total
Lines 78.7% 6101 / 7754
Functions 64.3% 2091 / 3250
Branches 47.8% 5182 / 10834

Core HTML report | API HTML report

Commit b4ddd789254a03a92e1d9c726ca88a504e6f23fc.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

gen-quality gen/

gen-quality: 84.5 / 100   (floor 84.5, +0.0)

  structure     86.5  x0.50   [fn 90.5 / file 82.6]
  cyclomatic    88.4  x0.25
  cognitive     76.6  x0.25

  409 functions across 31 files, 7702 lines (largest file 1044)
  max cc 56  max cognitive 44  max fn loc 152

Worst offenders (top 5 per axis; full lists in score.json):
  cyclomatic gen/xsd/analyze.py:311     report                             56
  cyclomatic gen/plates/build.py:956    _validate_config_against_ir        35
  cyclomatic gen/press/context.py:145   plate_context                      34
  cyclomatic gen/__main__.py:46         _ir                                23
  cyclomatic gen/tests/test_ir.py:102   _check_references                  20
  cognitive  gen/xsd/analyze.py:311     report                             44
  cognitive  gen/ir/resolve.py:119      flat_elements                      40
  cognitive  gen/tests/test_ir.py:102   _check_references                  38
  cognitive  gen/press/context.py:145   plate_context                      37
  cognitive  gen/xsd/analyze.py:207     _sccs                              37
  size       gen/xsd/analyze.py:311     report                             152
  size       gen/press/context.py:145   plate_context                      96
  size       gen/plates/build.py:533    _value_plate                       89
  size       gen/plates/build.py:956    _validate_config_against_ir        89
  size       gen/ir/resolve.py:119      flat_elements                      78

Commit b4ddd789254a03a92e1d9c726ca88a504e6f23fc.

@rpatters1

Copy link
Copy Markdown
Contributor

I'm wondering if we should create a similar class like ContinuationTie that models ties at the start of 2nd endings and the like. Not for this PR but something to think about.

@webern webern marked this pull request as ready for review July 2, 2026 19:03
@webern webern merged commit 2a8ae84 into main Jul 2, 2026
7 checks passed
@webern webern deleted the lv-ties branch July 2, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai Issues opened by, or through, a coding agent. area/mx::api area/mx::impl feature new feature request non-breaking fixes or implementation that do not require breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for LV ties

2 participants