Skip to content

fix: preserve staff-scoped staff-layout number="N"#326

Open
webern wants to merge 1 commit into
mainfrom
fix-staff-layout-number
Open

fix: preserve staff-scoped staff-layout number="N"#326
webern wants to merge 1 commit into
mainfrom
fix-staff-layout-number

Conversation

@webern

@webern webern commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Human Summary

TODO: human writes here

Summary

Both the per-measure <print> and score-level <defaults> readers took only the first
<staff-layout>'s distance and dropped its number attribute entirely, folding it into the single
unscoped SystemLayoutData::staffDistance. For a multi-staff part (e.g. a 2-staff piano/organ)
whose only staff-layout is scoped to staff 2, this silently misattributed the distance to staff 1
(the schema default for a missing number) instead of just dropping the attribute -- a real,
if quiet, correctness bug, not only a fidelity loss.

Added SystemLayoutData::staffDistances (a vector of a new StaffDistanceData: staffIndex +
staffDistance), populated only when the source's <staff-layout> had an explicit number
attribute; the existing singular staffDistance keeps its meaning for the common unscoped case.
Both readers (ScoreReader::scanForSystemInfo, LayoutFunctions::addStaffLayout) and both writers
(MeasureWriter::writeSystemInfo, LayoutFunctions::addSystemMargins) handle it symmetrically,
since <defaults> and per-measure <print> already share SystemLayoutData.

Testing

  • New staffScopedStaffDistance test (PrintLayoutRoundTripTest.cpp)
  • make test: all pass (4729 assertions in 379 test cases, plus the three examples)
  • make test-api-roundtrip: 158 passed, 0 failed (of 158 pinned; no regressions -- this fix
    corrects a wrong-staff misattribution rather than unlocking a new fully-passing file, since none
    of the affected corpus files had this as their sole blocker)
  • make fmt / make check: clean

References

@webern webern added bug software defect non-breaking fixes or implementation that do not require breaking changes api Affects the mx::api layer impl Affects the mx::impl layer ai Issues opened by, or through, a coding agent. labels Jul 7, 2026 — with Claude
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Coverage report

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

Metric Coverage Covered / Total
Lines 77.8% 28513 / 36640
Functions 74.3% 6352 / 8551
Branches 50.7% 22678 / 44751

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

Metric Coverage Covered / Total
Lines 80.7% 6401 / 7933
Functions 67.7% 2217 / 3275
Branches 49.5% 5496 / 11100

Core HTML report | API HTML report

Commit 798455f0292544538e266fed9bfd514f07e9cb02.

@github-actions

github-actions Bot commented Jul 7, 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 798455f0292544538e266fed9bfd514f07e9cb02.

Both the per-measure <print> and score-level <defaults> readers took only
the first <staff-layout>'s distance and dropped its number attribute
entirely, folding it into the single unscoped SystemLayoutData::staffDistance.
For a multi-staff part (e.g. a 2-staff piano/organ) whose only staff-layout
is scoped to staff 2, this silently misattributed the distance to staff 1
(the schema default for a missing number) instead of dropping the number.

Added SystemLayoutData::staffDistances (vector of new StaffDistanceData:
staffIndex + distance), populated only when the source's <staff-layout> had
an explicit number attribute; the existing singular staffDistance keeps its
meaning for the common unscoped case. Both readers (ScoreReader::
scanForSystemInfo, LayoutFunctions::addStaffLayout) and both writers
(MeasureWriter::writeSystemInfo, LayoutFunctions::addSystemMargins) handle
it symmetrically since defaults and per-measure print share SystemLayoutData.

## Testing

- [x] New `staffScopedStaffDistance` test (PrintLayoutRoundTripTest.cpp)
- [x] make test: all pass (4729 assertions in 379 test cases, plus examples)
- [x] make test-api-roundtrip: 158 passed, 0 failed (of 158 pinned; no
  regressions -- this fix corrects a wrong-staff misattribution rather than
  unlocking a new fully-passing file)
- [x] make fmt / make check: clean

Closes #281
@webern webern force-pushed the fix-staff-layout-number branch from c48404d to beefd31 Compare July 7, 2026 09:23
@webern webern changed the title fix: preserve staff-scoped &lt;staff-layout number="N"&gt; fix: preserve staff-scoped staff-layout number="N" Jul 7, 2026
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Coverage report

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

Metric Coverage Covered / Total
Lines 77.8% 28513 / 36640
Functions 74.3% 6352 / 8551
Branches 50.7% 22678 / 44751

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

Metric Coverage Covered / Total
Lines 80.8% 6434 / 7963
Functions 67.8% 2221 / 3276
Branches 49.6% 5524 / 11148

Core HTML report | API HTML report

Commit 1b8a0ab9ac2c26582882e07d4be01984f5f5b484.

@github-actions

github-actions Bot commented Jul 7, 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 1b8a0ab9ac2c26582882e07d4be01984f5f5b484.

{
/// A <staff-layout> scoped to one staff of a multi-staff part (MusicXML's number attribute):
/// the space between the bottom line of the previous staff and the top line of this one.
/// staffIndex is zero-based; a source with an explicit number attribute always populates

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.

A source? Meaning a file?

Since the coding agent has been tasked with round trip success, it is writing API documentation from that vantage-point which would be confusing to a user. Instead this documentation should explain how someone using mx::api to build up a score should use the interface. And the interface should avoid adding confusing things that exist solely to represent what is not music-notation-semantics-meaningful in a round-trip corpus test file.

/// staffIndex is zero-based; a source with an explicit number attribute always populates

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. api Affects the mx::api layer bug software defect impl Affects the mx::impl layer non-breaking fixes or implementation that do not require breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api: round-trip drops staff-layout

1 participant