Skip to content

feat: implement rehearsal writing in DirectionWriter#215

Merged
webern merged 3 commits into
mainfrom
claude/hello-emdyq2
Jun 20, 2026
Merged

feat: implement rehearsal writing in DirectionWriter#215
webern merged 3 commits into
mainfrom
claude/hello-emdyq2

Conversation

@webern

@webern webern commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Summary

DirectionWriter had no write path for RehearsalData, so rehearsal marks were silently dropped whenever a score was serialized. This adds a loop over directionData.rehearsals that emits <rehearsal> elements, mirroring the segno/coda pattern from #203.

While wiring up the round-trip test, two gaps in DirectionReader::parseRehearsal were also found and fixed: it was not calling getFontData, and it was not setting isColorSpecified before copying color attributes, so both fields were always lost on the read path. The isColorSpecified gap is the same bug class described in #207 (which covers the same pattern in parseWords — left for a separate PR).

A post-review fix also adds RehearsalEnclosure::unspecified as the new default for RehearsalData::enclosure. Previously the default was rectangle, so any rehearsal with no enclosure attribute in the source XML would silently gain enclosure="rectangle" on write-back.

Changes

  • RehearsalData.h — added RehearsalEnclosure::unspecified; changed RehearsalData default enclosure from rectangle to unspecified.
  • DirectionWriter::getDirectionLikeThings — new loop over rehearsals emitting <rehearsal> with text, position, font, color, and enclosure (skipped when unspecified).
  • DirectionReader::parseRehearsal — now reads fontData via getFontData and correctly gates colorData behind isColorSpecified.

Testing

  • rehearsalRoundTrip (DirectionWriterTest.cpp) — api → core → api at the impl layer; covers text, position, font, color, and enclosure shapes. Red before the fix, green after.
  • RehearsalSyntheticFileRead (DirectionDataTest.cpp) — loads data/synthetic/rehearsal.3.1.xml and asserts the read path surfaces text and enclosure. Pins the core → api direction.
  • RehearsalRoundTripXml (DirectionDataTest.cpp) — full MusicXML serialization/deserialization round-trip; covers text, enclosure, and font weight.
  • RehearsalUnspecifiedEnclosureNoPhantomAttribute (DirectionDataTest.cpp) — asserts that enclosure="rectangle" does not appear in serialized XML when enclosure is unspecified, and that the field round-trips as unspecified.
  • Full suite: 4101 assertions in 269 test cases, all pass.
  • make test-api-roundtrip baseline (1 pinned file): still passes.
  • make check (fmt-check): passes.

References

@webern webern added feature new feature request non-breaking fixes or implementation that do not require breaking changes area/mx::impl ai Issues opened by, or through, a coding agent. labels Jun 19, 2026 — with Claude
@github-actions

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 95005f43dd576fd479c89d7fb32188e32f1251d6.

@github-actions

Copy link
Copy Markdown

Coverage report

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

Metric Coverage Covered / Total
Lines 77.9% 28539 / 36624
Functions 74.4% 6360 / 8550
Branches 50.7% 22672 / 44725

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

Metric Coverage Covered / Total
Lines 72.3% 5373 / 7430
Functions 60.2% 1824 / 3029
Branches 43.1% 4481 / 10386

Core HTML report | API HTML report

Commit 95005f43dd576fd479c89d7fb32188e32f1251d6.

@github-actions

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 8115fa813386940d6b38631dc55e71c037ada8d3.

@github-actions

Copy link
Copy Markdown

Coverage report

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

Metric Coverage Covered / Total
Lines 77.9% 28539 / 36624
Functions 74.4% 6360 / 8550
Branches 50.7% 22672 / 44725

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

Metric Coverage Covered / Total
Lines 72.3% 5375 / 7432
Functions 60.2% 1824 / 3029
Branches 43.2% 4483 / 10387

Core HTML report | API HTML report

Commit 8115fa813386940d6b38631dc55e71c037ada8d3.

webern added 2 commits June 20, 2026 17:13
Fixes #205 and closes #134.

`DirectionWriter` was missing a write path for `RehearsalData`, causing
rehearsal marks to be silently dropped on output. Add a loop over
`directionData.rehearsals` that emits `<rehearsal>` elements, mirroring
the segno/coda pattern from PR #203.

While wiring up the round-trip test, also fix two gaps in
`DirectionReader::parseRehearsal`: it was not reading `fontData` (calling
`getFontData`) and was not setting `isColorSpecified` before copying color
attributes, so those fields were always lost on the read path.

Tests added:
- `rehearsalRoundTrip` in `DirectionWriterTest.cpp`: api → core → api
  round-trip at the impl layer, covering text, position, font, color,
  and enclosure.
- `RehearsalSyntheticFileRead` in `DirectionDataTest.cpp`: loads
  `data/synthetic/rehearsal.3.1.xml` and asserts the read path surfaces
  text and enclosure.
- `RehearsalRoundTripXml` in `DirectionDataTest.cpp`: full MusicXML
  serialization/deserialization round-trip via `mxtest::roundTrip`
  covering text, enclosure, and font weight.
… on write

When a rehearsal element had no enclosure attribute in the source XML,
the reader left RehearsalData::enclosure at its default (rectangle), and
the writer would then emit enclosure="rectangle", silently modifying the
document on round-trip.

Fix by adding an `unspecified` sentinel to RehearsalEnclosure, changing
the RehearsalData default to `unspecified`, and skipping the enclosure
setEnclosure call in DirectionWriter when the value is unspecified.

Adds RehearsalUnspecifiedEnclosureNoPhantomAttribute test that asserts
no enclosure attribute appears in the serialized XML and that the field
round-trips as unspecified.
@webern webern force-pushed the claude/hello-emdyq2 branch from 762e282 to a7c6684 Compare June 20, 2026 15:13
@github-actions

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 baeec0005296c8c36753594a904168b8d933d3dc.

@github-actions

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 259076d1aefd2dcafa86732fc33965cf5d6779ca.

@webern webern merged commit bb71cb6 into main Jun 20, 2026
7 checks passed
@webern webern deleted the claude/hello-emdyq2 branch June 20, 2026 15:25
@github-actions

Copy link
Copy Markdown

Coverage report

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

Metric Coverage Covered / Total
Lines 77.9% 28539 / 36624
Functions 74.4% 6360 / 8550
Branches 50.7% 22672 / 44725

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

Metric Coverage Covered / Total
Lines 72.3% 5375 / 7432
Functions 60.2% 1824 / 3029
Branches 43.2% 4483 / 10387

Core HTML report | API HTML report

Commit 259076d1aefd2dcafa86732fc33965cf5d6779ca.

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::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.

impl: implement rehearsal writing in DirectionWriter

1 participant