Skip to content

feat: model the metronome-note form and beat-unit-tied - #363

Merged
webern merged 1 commit into
mainfrom
claude/direction-types-ordering-9l60vv-f
Jul 19, 2026
Merged

feat: model the metronome-note form and beat-unit-tied#363
webern merged 1 commit into
mainfrom
claude/direction-types-ordering-9l60vv-f

Conversation

@webern

@webern webern commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Human Summary

Unfortunately this is quite complicated, but it appears to correctly model the various forms of metronome markings that MusicXML supports.

Summary

Second and final metronome slice of #324. TempoData's enum-plus-parallel-payload shape is replaced by a TempoChoice choice class (the TimeChoice / PercussionDataChoice pattern) over the three <metronome> bodies: beats-per-minute, metric modulation, and the note-relation form. The unreachable unspecified tempo sentinel is gone — a default/empty tempo is skipped by the writer on a content check, so nothing throws.

The note-relation form — metric relationships drawn with note figures (metronome-note and metronome-relation, plus per-figure metronome-beam, metronome-dot, metronome-tied, and metronome-tuplet, and the metronome-arrows flag) — is modeled in a new NoteRelationData.h with dedicated mini-structs. They reuse only the leaf enums (DurationName, Beam); a metronome note figure has none of the pitch/stem/staff/voice machinery of a real note, so NoteData is not reused. beat-unit-tied (a beat-unit tied to the preceding one, e.g. "quarter + eighth = 120") is modeled on both the beats-per-minute and metric-modulation forms.

Every part of <metronome> now round-trips except per-minute's own font override, which is a documented drop — the font belongs on the metronome element, and no real-world file uses the per-minute font.

Breaking: TempoData::tempoType and the parallel beatsPerMinute / metricModulation fields are replaced by TempoData::choice.

Testing

  • New roundTripNoteRelation (strong whole-body equality check), roundTripBeatUnitTied, plus roundTripMetricModulation/roundTripBpm/attributes migrated to the choice API
  • Full unit suite passes (5105 assertions in 449 test cases)
  • Round-trip regression 284/284 pinned, no regressions
  • Round-trip baseline 275 -> 284 (all eight metronome-note fixtures plus beat-unit-tied)

References

@webern webern added feature new feature request breaking fixes or implementation that 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 19, 2026 — with Claude
@webern

webern commented Jul 19, 2026

Copy link
Copy Markdown
Owner Author

/coverage

@github-actions

Copy link
Copy Markdown

Coverage report

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

Metric Coverage Covered / Total
Lines 77.8% 28514 / 36658
Functions 74.2% 6352 / 8561
Branches 50.7% 22680 / 44751

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

Metric Coverage Covered / Total
Lines 85.7% 8175 / 9534
Functions 78.7% 3244 / 4123
Branches 53.1% 7183 / 13535

Core HTML report | API HTML report

Commit 88c98f987a82f9fabe1f55e99fddc14cc515d1c0.

Base automatically changed from claude/direction-types-ordering-9l60vv-e to main July 19, 2026 13:42
Second metronome slice of #324. TempoData's enum-plus-parallel-payload
shape is replaced by a TempoChoice choice class (the TimeChoice /
PercussionDataChoice pattern) over the three <metronome> bodies:
beats-per-minute, metric modulation, and the note-relation form. The
unreachable 'unspecified' tempo sentinel is gone -- a default/empty tempo
is skipped by the writer on a content check, so nothing throws.

The note-relation form -- metric relationships drawn with note figures
(metronome-note and metronome-relation, plus per-figure metronome-beam,
metronome-dot, metronome-tied, and metronome-tuplet, and the
metronome-arrows flag) -- is modeled in NoteRelationData.h with dedicated
mini-structs that reuse only the leaf enums (DurationName, Beam); the
metronome note vocabulary is far narrower than NoteData. beat-unit-tied
(a beat-unit tied to the preceding one) is modeled on both the
beats-per-minute and metric-modulation forms.

Round-trip baseline 275 -> 284 (all eight metronome-note fixtures plus
beat-unit-tied). Every part of <metronome> now round-trips except
per-minute's own font override, a documented drop -- the font belongs on
the metronome element.

Breaking: TempoData::tempoType and the parallel beatsPerMinute /
metricModulation fields are replaced by TempoData::choice.
@webern
webern force-pushed the claude/direction-types-ordering-9l60vv-f branch from 88c98f9 to 028b8ff Compare July 19, 2026 13:49
@webern
webern merged commit 5d9804a into main Jul 19, 2026
7 checks passed
@webern
webern deleted the claude/direction-types-ordering-9l60vv-f branch July 19, 2026 13:59
webern added a commit that referenced this pull request Jul 19, 2026
## Human Summary

Changes the way pedals are modeled into two different things. Pedal
marks, which are the old fashioned Ped and * marks, and a separate type
for more modern lines.

## Summary

`DirectionReader::parsePedal` handled only `PedalType` start/stop and
silently dropped
`sostenuto`, `change`, `continue`, `discontinue`, and `resume`, even
though `core::PedalType`
carries all seven tags. This was the last unmodeled item in #324.

The shape decision (called for in #324) was to store the pedal type as
one fact in one field
rather than scatter it across start/stop/other vectors. This replaces
the `pedalStarts` /
`pedalStops` spanner vectors on `DirectionData` with a single
`std::vector<PedalData> pedals`,
where each `PedalData` carries a `PedalKind` enum covering every
pedal-line type. Each pedal event
stands on its own and is placed at its own tick, so the model has no
neighbor-dependent meaning.

- New `PedalData.h`: the `PedalKind` enum (`start`, `stop`, `sostenuto`,
`change`, `continueLine`,
  `discontinue`, `resume`) and the `PedalData` struct.
- Reader: line pedals (`line="yes"`, plus the inherently line-formatting
types
`sostenuto`/`change`/`continue`/`discontinue`/`resume`) go to `pedals`;
sign start/stop still
  route through `MarkData` as `MarkType::pedal` / `damp`, unchanged.
- Writer: a single `emitPedal` maps `PedalKind` back to
`core::PedalType`, emits `line="yes"`, and
  emits nothing for an unspecified kind.

Only two corpus files use these types (`lysuite/ly31a_Directions.xml`,
`lysuite/ly33a_Spanners.xml`)
and both fail first on unrelated features, so this does not flip any
file to PASS. It is validated
by a focused unit test that round-trips each pedal type through the api.

This is a breaking change: it reshapes `DirectionData`'s pedal fields.

## Testing

- [x] New `PedalAllKinds` round-trips all seven pedal types through the
api; `PedalPlacement` covers
  placement (`*Pedal*`: 44 assertions in 5 test cases)
- [x] Full unit suite passes (5130 assertions in 451 test cases)
- [x] api round-trip regression unchanged (284 passed, 0 failed of 284
pinned)

## References

- Closes #324 (the `symbol` item from that worklist is tracked
separately by #294 and is not
  included here)
- Follows the merged direction-type stack #358#363
- Part of #208
- Spanner-numbering context: #320, #351
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 breaking fixes or implementation that require breaking changes feature new feature request impl Affects the mx::impl layer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant