Skip to content

feat(api): support the full time-signature model#321

Open
webern wants to merge 1 commit into
mainfrom
claude/eloquent-turing-us0rtv
Open

feat(api): support the full time-signature model#321
webern wants to merge 1 commit into
mainfrom
claude/eloquent-turing-us0rtv

Conversation

@webern

@webern webern commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Human Summary

This is now on its third design. A simple version of time signatures and a complex, MusicXML spec complete version, coexist in a choice-like object called TimeChoice. The idea is to hide away the, really rather difficult complexities of time signatures unless you really need it.

Summary

Replaces the flat single-pair TimeSignatureData with a plain data struct that models every MusicXML time shape while keeping the 99% case a one-liner (setSimple("4", "4") to author, beats()/beatType() to read).

The shape follows the proposal in #268: TimeSignatureData holds a public std::vector<TimeSignatureComponent> — one pair is the ordinary simple case, more than one is a composite/additive meter like 2/4 + 3/8 (a compound numerator like 3+2/8 remains a single pair). The rare cases are quarantined in two std::optional fields per the api presence/absence conventions:

  • senzaMisura (std::optional<std::string>) — engages unmetered music; the string is the display glyph (usually empty, sometimes "X"). When set, components/symbol/interchangeable are ignored by the writer.
  • interchangeable (std::optional<InterchangeableTimeSignature>) — the alternate meter of a dual pair like 3/4 (6/8), with its own components, relation, symbol, and separator.

Also new: per-staff time signatures (time number="N") via a normally-empty MeasureData::staffTimeSignatures vector using the same staffIndex convention as KeyData (the singular MeasureData::timeSignature remains the unscoped case); the widened symbol vocabulary (note, dotted-note — previously dropped); and the separator attribute.

Implementation:

  • TimeReader now translates every time element in a measure (previously it read only the first pair of the first element and silently dropped everything else); MeasureReader routes by staff scope and carries per-staff overrides forward alongside the singular value (Cursor gains the carried per-staff state).
  • PropertiesWriter::writeTime regenerates all shapes symmetrically; MeasureWriter emits one element per non-implicit entry.
  • Converter gains bidirectional TimeSymbol/TimeSeparator/TimeRelation tables (core normal() folds into api unspecified).

This is a breaking change to the public api: the beats/beatType string fields are replaced by components (with setSimple and first-pair beats()/beatType() conveniences), and the unused isEqualTo method is removed. Call-site migration is mechanical (see the migrated fixtures in this diff).

Testing

  • New unit coverage in TimeSignatureApiTest.cpp: composite, senza-misura with/without glyph, interchangeable (composite alternate, relation/symbol/separator), widened symbols, separator, per-staff routing + carry-forward, and nested-field equality
  • make test passes (4800 assertions in 389 test cases) + all examples
  • make test-api-roundtrip passes, 163/163 pinned
  • Four corpus files newly pass the api round-trip and are pinned in roundtrip-baseline.txt: ly11d_TimeSignatures_CompoundMultiple, ly11e_TimeSignatures_CompoundMixed, ly11f_TimeSignatures_SymbolMeaning, ly11h_TimeSignatures_SenzaMisura (discovery mode: zero regressions)
  • Strict unity build (CMAKE_UNITY_BUILD=ON, batch size 0) clean; make check clean

Notes: rpatters1/timesigs_composite-ref.musicxml and the synthetic interchangeable/senza-misura/time-relation files now round-trip their time signatures correctly but remain blocked by unrelated diffs (a music-font whitespace normalization; the api always emitting divisions which those minimal synthetic files omit).

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 6, 2026 — with Claude
@github-actions

github-actions Bot commented Jul 6, 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 / 8552
Branches 50.7% 22678 / 44751

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

Metric Coverage Covered / Total
Lines 80.9% 6448 / 7974
Functions 67.7% 2224 / 3284
Branches 49.8% 5542 / 11132

Core HTML report | API HTML report

Commit 302722164546a53f08e0194c3f6204a040bb3c5b.

@github-actions

github-actions Bot commented Jul 6, 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 302722164546a53f08e0194c3f6204a040bb3c5b.

@webern webern force-pushed the claude/eloquent-turing-us0rtv branch from 96c10f3 to 16274e6 Compare July 7, 2026 09:24
@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 / 8552
Branches 50.7% 22678 / 44751

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

Metric Coverage Covered / Total
Lines 81.0% 6481 / 8004
Functions 67.8% 2228 / 3285
Branches 49.8% 5570 / 11180

Core HTML report | API HTML report

Commit 156117869514f0a8463e5a473ccf4c0c7291398b.

@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 156117869514f0a8463e5a473ccf4c0c7291398b.

@webern webern force-pushed the claude/eloquent-turing-us0rtv branch from 16274e6 to 1cd06bf Compare July 8, 2026 21:16

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

Let's try again.

Comment thread src/include/mx/api/MeasureData.h Outdated
// The unscoped <time> (applies to all staves). staffIndex is INDEX_UNSPECIFIED here.
TimeSignatureData timeSignature;

// Normally empty: one entry per staff-scoped <time number="N">. Each entry's staffIndex is the

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.

I think this is fine and it works. It's just that the comment is a bit hard to understand. It should be something like this:

Only use this is if you need different time signatures on different staves.

Furthermore, we should not put a staff index on the time signature structure. Instead this should be a map or a vector of pairs naming the staff index and the time signature override that goes with it.

Comment thread src/include/mx/api/TimeSignatureData.h Outdated
namespace api
{

// -------- enums (unspecified-first, per mx::api convention) --------

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.

Don't make separator comments like this.

Comment thread src/include/mx/api/TimeSignatureData.h Outdated
common,
cut,
singleNumber
singleNumber,

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.

I don't think this should be a list of symbols but then have this one item that's not exactly a symbol but rather a marker that tells you (I guess?) look somewhere else for the single number that is the symbol?

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

// beats/beatType are free-form strings by design: they may hold compound values ("3+2"),
// decimals ("1 + 2.5"), or compound denominators ("8 + 16").
struct TimeSignatureComponent

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.

I don't like this class because to me this looks like what a "normal" time signature should be. This is basically the one that I would want to interact with 99% of the time, but it's called a "component".

Comment thread src/include/mx/api/TimeSignatureData.h Outdated
// accessors never throw. The orthogonal display/scoping attributes are plain public fields.
//
// Defaults to an implicit, simple 4/4.
class TimeSignatureData

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.

I don't want this class buried under all this new stuff. I'd rather have all the new, complicated stuff in a separate file. Name TBD. TimeSignatureX where X is something like
Components, Support, Items, I don't know. Something to get this garbage interface out of the way of the simpler use cases.

Comment thread src/include/mx/api/TimeSignatureData.h Outdated
return std::holds_alternative<MeteredTimeSignature>(mySignature);
}

bool isSenzaMisura() const

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 interface is a disaster. I mean the whole thing is terrible. We need to try again.

I think we need to have TimeSignatureData for the simple use case, and something like ComplexTimeSignature for everything else.

Then maybe there's something like a TimeChoice class that switches between the two.

@github-actions

github-actions Bot commented Jul 8, 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 / 8552
Branches 50.7% 22678 / 44751

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

Metric Coverage Covered / Total
Lines 80.9% 6481 / 8009
Functions 67.8% 2228 / 3285
Branches 49.8% 5568 / 11186

Core HTML report | API HTML report

Commit d5a425752ce17d92fa004a46cd7bdbccc17179b4.

@github-actions

github-actions Bot commented Jul 8, 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 d5a425752ce17d92fa004a46cd7bdbccc17179b4.

Redesign the time-signature api around a TimeChoice that switches
between the simple case (the ordinary N/D you want 99% of the time) and
a complex case that quarantines everything unusual. The simple type and
the complex machinery live in separate headers, so ordinary 4/4 work
never has to look at the complex surface.

Public shape (src/include/mx/api/):
- TimeSignatureData.h: TimeFraction (shared beats/beat-type leaf) and
  TimeSignatureData { TimeSignatureSymbol symbol; TimeFraction fraction; }
  where the symbol is the narrow {unspecified, common, cut}.
- ComplexTimeSignature.h: the full ComplexTimeSymbol vocabulary,
  TimeSeparator, TimeRelation, InterchangeableTimeSignature,
  MeteredTimeSignature, and ComplexTimeSignature -- itself a
  { metered, senzaMisura } choice mirroring the spec's one real fork.
- TimeChoice.h: the { simple, complex } switch, carrying the
  whole-<time> attributes (isImplicit, display).

Guarantees:
- TimeChoice::complex auto-collapses a simple-equivalent meter (one
  fraction, no interchangeable, no separator, a symbol the simple case
  can express) back to simple, so a plain N/D can never hide in the
  complex arena and there is one canonical representation.
- The two symbol enums do not overlap on the unusual values:
  single-number/note/dotted-note exist only in the complex vocabulary,
  so they cannot appear on the simple type.

Per-staff signatures move to std::map<int, TimeChoice>
staffTimeSignatures on MeasureData (keyed by staff index; no staff
index baked into the signature type).

Choice-type logic (factories, accessors, equality, auto-collapse) lives
in TUs under src/private/mx/api/, keeping the headers declaration-only.

impl: TimeReader translates each core::Time to a TimeChoice;
PropertiesWriter::writeTime regenerates all shapes and takes the staff
index separately; MeasureReader/MeasureWriter route by staff scope and
carry per-staff overrides forward; Converter gains narrow/full symbol,
separator, and relation tables.

Four corpus files newly pass the api round-trip and are pinned
(ly11d/e composite, ly11f symbols, ly11h senza-misura).
@webern webern force-pushed the claude/eloquent-turing-us0rtv branch from 1cd06bf to 0a3855c Compare July 8, 2026 22:19
@github-actions

github-actions Bot commented Jul 8, 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 / 8552
Branches 50.7% 22678 / 44751

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

Metric Coverage Covered / Total
Lines 81.1% 6565 / 8093
Functions 68.1% 2250 / 3306
Branches 49.8% 5637 / 11317

Core HTML report | API HTML report

Commit 52649b0a7f0c21fdcba3401fb3ba170b6c92396b.

@github-actions

github-actions Bot commented Jul 8, 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 52649b0a7f0c21fdcba3401fb3ba170b6c92396b.

@webern webern requested a review from Copilot July 9, 2026 20:51

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@webern webern changed the title feat: redesign TimeSignatureData for the full time model feat(api): support the full time signature model Jul 9, 2026
@webern webern requested a review from Copilot July 9, 2026 20:54

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@webern webern changed the title feat(api): support the full time signature model feat(api): support the full time-signature model Jul 10, 2026
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.

api: time signature improvements

2 participants