feat: model editorial (footnote/level) in mx::api#236
Conversation
gen-quality
|
Coverage reportCore-dev coverage
|
| 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 | 78.2% | 5940 / 7594 |
| Functions | 62.4% | 1979 / 3173 |
| Branches | 47.5% | 5008 / 10537 |
Core HTML report | API HTML report
Commit ffcf49bf4d2a4295aab496dd059c9aa4228d0ae0.
| enum class StartStopSingle | ||
| { | ||
| unspecified, | ||
| start, | ||
| stop, | ||
| single | ||
| }; | ||
|
|
||
| // Mirrors the MusicXML <level> 'size' attribute (symbol-size). |
There was a problem hiding this comment.
This strikes me as one of things we should try to simplify in the mx::api model instead of mirroring the way MusicXML models it. In general, we are trying to get away from MusicXML's procedural "stop"/"start" semantics (i.e. requiring stateful parsing) and represent data more statically whenever we can.
Add a reusable api::EditorialData (footnote + level) and attach it to PartGroupData (the start group) and DirectionData, threading it through the impl reader/writer layers so <footnote>/<level> survive an api round-trip instead of being silently dropped. - New api types: EditorialData, FootnoteData (formatted-text, mirroring WordsData), and LevelData with its StartStopSingle/SymbolSize enums. - Shared EditorialFunctions.h translates the core editorial groups (EditorialGroup, EditorialVoiceDirectionGroup) in both directions. - Converter gains StartStopSingle and SymbolSize bridge maps. - EditorialApiTest proves footnote+level round-trip on a part-group and a direction, using distinct non-default values per field. Part-group stop editorial is intentionally not modeled, since MusicXML ignores child element values at a group stop.
384f715 to
c14698f
Compare
gen-quality
|
Coverage reportCore-dev coverage
|
| 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 | 78.2% | 5940 / 7594 |
| Functions | 62.4% | 1979 / 3173 |
| Branches | 47.5% | 5008 / 10537 |
Core HTML report | API HTML report
Commit 60f8ee5e408cfa319d1924d2b156c7b8c2b16a9c.
|
Closing without merging — we're dropping editorial ( Rationale: editorial appears in zero real-world corpus files. Every What happens to the pieces of this PR:
Superseded by #238 (now based directly on #235). Generated by Claude Code |
…tures (#238) Brings the audit's synthetic `segno`/`coda` fixtures onto the strict api read -> write -> read gate. ## What 1. **Round-trip the editorial-voice `<voice>` on a `<direction>`** (read from `editorialVoiceDirection().voice()`, written back). Today a direction's own `<voice>` is dropped. The write is emitted only when the direction has direction-type content, so a voice carried in from a figured-bass/harmony element never produces an empty `<direction>`. `<voice>` is common in the wild corpus, so this is real fidelity, not synthetic-only. 2. **Pin `synthetic/{segno,coda}.{3.0,3.1}.xml`** in `roundtrip-baseline.txt`. To make them round-trip losslessly the fixtures are tidied: - stripped the `<footnote>`/`<level>` editorial boilerplate the audit generator stamps onto every probe (editorial appears in **zero** real-world files, so the api does not model it — see #236, closed) - added a `<divisions>` + a `<note>` so the writer's first-measure `<divisions>` is legitimate instead of a spurious injection into a note-less measure They still isolate the segno/coda surface (3.0 base attributes; 3.1 adds id + smufl) and now also exercise a part-group and direction `<voice>`/`<staff>` through the public `DocumentManager` path. ## Why a real note instead of an impl fix for `<divisions>` A note-less measure that the writer fills with a synthesized `<divisions>` is the #228 writer-policy issue. A naive "suppress divisions when the part has no notes" guard regresses `ksuite/k016a_Miscellaneous_Fields.xml`, which legitimately carries `<divisions>` in a note-less measure — distinguishing "source had it" from "api invented it" needs divisions-presence tracking in the public api, out of scope here. So the fixtures carry a real note; the writer-policy gap stays open under #228. ## Follow-up A separate PR on top of this one strips the editorial boilerplate from every *other* synthetic file that isn't the dedicated `footnote`/`level` probe, so the synthetic corpus stops implying api features the wild corpus never uses. ## Testing - [x] api round-trip regression gate: 38 passed, 0 failed (was 34; +4 synthetic) - [x] discovery: no regressions - [x] Full `mxtest`: 4268 assertions in 307 test cases, all pass - [x] corert: 832 test cases pass, pinned corpus count unchanged - [x] `clang-format` clean; audit `*.features.xml` sidecars + `corpus.xml` regenerated ## References - Stacked on #235 / #204 (segno/coda api round-trip) - Supersedes #236 (editorial dropped); resolves the direction-voice part of #237, with the `<divisions>` writer-policy part staying with #228 - Part of #208 (api round-trip coverage)
) ## Summary The audit generator stamps `<footnote>`/`<level>` editorial onto nearly every synthetic single-feature fixture, but editorial appears in zero real-world corpus files — all 382 editorial-bearing files were under `data/synthetic/`. That boilerplate implied mx::api needed editorial support it does not actually need for the wild corpus, which is why #236 was closed without merging. This removes the `<footnote>x</footnote>`/`<level>x</level>` boilerplate from every synthetic fixture except the dedicated footnote/level probes, which keep only their own target element. One uniform rule does this safely: the boilerplate is always the plain `>x<` form, while each probe's target is always attributed (`<footnote default-x=...>`, `<level reference=...>`), so deleting the plain form strips the noise everywhere and preserves every probe's intent. - 382 fixtures changed (379 stripped fully; the footnote/level probes keep their attributed target) - `*.features.xml` sidecars and `corpus.xml` regenerated ## Testing - [x] corert: 832 test cases pass, pinned corpus count unchanged (the now-empty part-groups round-trip) - [x] api round-trip regression gate: 38/38 - [x] Full mxtest: 4268 assertions in 307 test cases (ApiLoadSmokeTest walks every stripped file) - [x] No code changes; `clang-format` not applicable ## Notes While regenerating sidecars, `audit files --force` also corrected a stale `<audited-version>` (3.0 → 3.1) in `data/custom/segno_coda_roundtrip.3.1.features.xml`. That file belongs to #235, so the correction was reverted out of this PR to keep it focused — worth fixing on the #235 branch. ## References - Stacked on #238 (base branch `claude/issue-237-roundtrip-vsvcas`) - Follows from #236 (editorial dropped from mx::api; closed) - Progresses #221 — footnote/level are no longer dropped across ~373 classifier files now that the synthetic boilerplate is gone - Part of #208 (api round-trip corpus coverage)
Summary
Models MusicXML editorial information (
<footnote>and<level>) inmx::apiso it survives an api round-trip instead of being silently dropped.Adds a reusable
api::EditorialData(footnote+level) and attaches it to:PartGroupData— editorial on the part-group startDirectionData— editorial on the<direction>(the editorial-voice<voice>is already represented byDirectionData.voice)New api types:
EditorialData,FootnoteData(formatted-text, modeled onWordsData), andLevelDatawith newStartStopSingle/SymbolSizeenums. A sharedimpl/EditorialFunctions.htranslates the core editorial groups (EditorialGroup,EditorialVoiceDirectionGroup) in both directions, andConvertergains the two enum bridge maps.FootnoteDatareads color with the correctisColorSpecified = color().has_value()gate, avoiding the bug tracked in #207.Part-group stop editorial is intentionally not modeled — MusicXML ignores child element values at a group stop.
Scope
This makes footnote/level round-trip through the api, proven by unit tests. It does not pin the dense
synthetic/{segno,coda}.*probes to the strict round-trip baseline: after this change those files still diverge on two unrelated gaps — the writer materializing<divisions>in a note-less measure (the #228 family) and a dropped direction<voice>— tracked in a separate follow-up.Testing
EditorialApiTest: footnote+level round-trip on a part-group and a direction, with distinct non-default values per field (29 assertions, 2 cases)mxtest: 4297 assertions in 309 test cases, all passclang-formatcleanNotes
Stacked on #235 (base branch
claude/issue-204-plan-vsvcas); review/merge that first. GitHub will retarget this tomainonce #235 merges.References
api.features.xmlaudit update is the natural next step) and investigate api dropping elements marked support=full on round-trip #219 (part-group/direction children dropped on round-trip)parseWordsnever setsWordsData.isColorSpecified— words color does not round-trip #207Generated by Claude Code