Commit Graph

2 Commits

Author SHA1 Message Date
Levi Neuwirth e5efe22d5b layout-ir: staff_step_pitch, the clef-relative position inverse
The inverse of staff_position: a staff step (a clicked staff height) back to the
diatonic pitch (nominal, octave) written there under a clef. This is the vertical
half of the click-to-insert inverse the GUI will use — turning where you click on
a staff into the pitch to place.

It mirrors the forward map's clef and octave-shift convention exactly, computes
the diatonic index in i64 so an extreme step cannot overflow the intermediate,
and returns None for a percussion clef (no diatonic mapping) or a position whose
octave falls outside i8 (rather than wrapping to a wrong-looking octave). A
round-trip test covers treble/bass/alto/tenor and octave-shifted (8va/8vb)
trebles across octaves and nominals, plus the out-of-range refusals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAtEiJtt9yKVV1zjKYmZhS
2026-06-29 17:28:23 -04:00
Levi Neuwirth 91c9f01bb5 Agent I Phase 2-3: engrave recognizable notation from Score to SVG
Turn the Score -> layout-IR -> SVG pipeline from placeholder glyphs into real
music notation, rendered through the stub solver and the real Engraver alike.

to_constrained (the spacing pass) now dispatches each layout object to the
notation primitive that represents it, on a column-based spring model:

  - Pitch -> notehead at its clef-relative staff position; chord pitches share
    one column slot. StaffInstance -> clef glyph. Staff -> five staff-line
    strokes (the bottom line its anchor, four synthesized). Pitched Event ->
    stem stroke; Measure -> barline glyph; rest Event -> rest glyph.
  - Phase 3 ornaments as synthesized glyphs: a spelling's full accidental stack
    left of its notehead, a key signature's clef-relative sharp/flat zigzag in
    the lead, and a measure's numerator/denominator time-signature digit pair.
  - A tied decomposition draws one notehead/stem/rest per component (offsets
    honored, not collapsed). Active clef and key resolve by time, not vector
    order. The lead area reserves clef + key-signature width.
  - Coverage/surjection preserved so the round-trip holds: each laid-out object
    is covered by exactly one exact-provenance primitive, and derived primitives
    (staff lines, components, accidentals, key/time glyphs) are synthesized from
    a laid-out source. Engraving-coverage gaps (missing spelling, unbundled
    glyph) are surfaced as ConstrainedLayoutIR diagnostics, not silently
    defaulted. A measure depends on the time signature it displays.

The Engraver re-spaces glyphs AND the strokes that track them through one
collision-aware coordinate map (per-slot left/right bearings, pairwise
advances), so stems / barlines / staff lines stay attached and a note's
accidental never overlaps the previous note. validate() now rejects empty
spring slots -- the contract that map relies on -- and to_constrained never
emits one (slots are realized by glyph occupancy).

Bundle the genuine Bravura outlines and metrics for time-signature digits 0-9
(regenerated from the SHA-pinned font via tools/extract_bravura_outlines.py),
replacing an inconsistent hand-written placeholder.

Regions tile left-to-right (no page casting-off yet). Goldens regenerated into
recognizable notation. Full gate green: build, fmt, clippy, 574 tests,
conformance scale 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:46:33 -04:00