Skip to content

feat(render): write chords and multiple voices on numbered staves - #3017

Merged
Danielku15 merged 1 commit into
developfrom
feat/numbered-chords-voices
Nov 7, 2026
Merged

Danielku15 merged 1 commit into
developfrom
feat/numbered-chords-voices

Conversation

@Danielku15

Copy link
Copy Markdown
Member

Note

AI-authored disclosure (alphatab-ai-authored-v1)

Portions of this content were authored by an AI agent. The agent has read
AGENTS.md and the human submitter accepts responsibility for
compliance with the rules in that document.

Issues

Fixes #2437

Proposed changes

Numbered (jianpu) staves wrote a single number per beat and only the first voice, as Guitar Pro does. Real jianpu writes chords and several parts, so alphaTab dropped musical content. Numbered staves now write every chord note and every voice, following printed jianpu: 朝鲜歌曲集(简谱版), 音乐出版社 1957; 战地新歌, 1970s. Piano jianpu sheets served as a secondary source.

Notation

  • Chords stack their numbers upwards from the line of single numbers, lowest note at the bottom.
    • Every number keeps its own octave dots, accidental and augmentation dot.
    • Duration underlines and dashes belong to the lowest number.
    • Ties are drawn per note; effect slurs of a chord are merged into one arc.
  • Voices are written as separate rows, like the parts of a choir system: voice 1 on top, beats aligned in time.
    • A system without further voices keeps a single row. An empty first voice keeps its blank row.
    • Further rows use the secondary voice colour.
    • The time signature and the repeat dots are written in front of every row.
  • Underlines and low octave dots (also affects single-voice output):
    • Low octave dots go below the duration underlines, as in print. They used to sit between the number and the underlines.
    • In groups mixing durations, the underline spanning the group sits directly below the numbers, and the extra lines of shorter notes go below it.
  • Single note mode: the previous display stays available as NumberedStaffConfig.notes = NumberedNoteMode.SingleNote. In alphaTex this is \staffDisplay numbered { notes singleNote } or \defaultStaffDisplay. It writes the highest note of voice 1.

Layout

  • Slots. Every number sits on a half-line step of the numbered renderer's existing (invisible) line grid, like a note head on a five-line staff. The renderer's line count follows from the rows and chord positions it needs. Bar lines, staff height, skylines and overflows then come from the existing code.

  • Alignment along a system. Each bar records what its chords and rows need while it is laid out. When the bars of a system are scaled, the first bar of the staff merges these needs. They are shared through the staff's existing shared layout data, and every bar moves its numbers onto the shared steps. Chord numbers and rows therefore line up along a system, and the horizontal spacing is not affected.

    • Bars whose own steps already match the system's are left alone.
    • Staves with a single voice and no chords collect nothing and allocate nothing.
  • Row spacing. Rows are spaced by their content, per bar:

    • what hangs below the upper row (underlines, low dots);
    • two staff spaces;
    • what rises above the lower row (chord numbers, high dots, ties, tuplets, grace chords).

    Where a time signature is written in front of both rows, the rows are at least its height plus the staff content padding apart. The system takes the maximum over its bars. Printed jianpu keeps about 1.6–1.9 digit heights between the rows; two staff spaces is about 1.5.

Generic engine changes (please review these specifically)

  • LineBarRenderer exposes staff rows (staffRowLineCount, staffRowCount, getStaffRowOffset). Header bar lines, repeat dots and dead-slapped crosses are laid out within the first row. Defaults keep the current behaviour for every other notation.
  • HorizontalScreenLayout scales the bars of its system once the system holds all of them, as the page layouts already do. Before, it scaled every bar right after adding it.
  • RenderStaff.staffBottom is reset and registered together with the staff overflows. Before, it was a running maximum that also kept bars reverted from a system.
  • BarRendererBase.remeasureVertically() (protected) measures overflows again after a vertical move, without a new layout.
  • Fixed along the way: in a bracketed track with two numbered staves, the bar number no longer runs into the bracket.

API (additive, nothing removed)

  • NumberedNoteMode { AllNotes = 0, SingleNote = 1 }
  • NumberedStaffConfig.notes (default AllNotes via RenderStylesheet.numberedConfig)
  • EngravingSettings.numberedChordNoteGap
  • alphaTex property notes on \staffDisplay numbered and \defaultStaffDisplay, with import and export

The default display changes visibly: numbered staves now show all chord notes and voices. Single note mode restores one number per beat. This is listed under the upgrade notes of the 1.10 release notes.

Tests

  • NumberedChords unit tests, rendered with the visual-test alphaSkia setup so values match on TS, C# and Kotlin. They assert explicit positions through the bounds lookup:
    • chord steps along a system, with and without octave dots;
    • row distances (content, time signature, per-bar pairing, empty first voice, grace chords);
    • time alignment of voices;
    • ties across system breaks;
    • single note mode;
    • an accidental and an augmentation dot per chord number;
    • time signature and repeat dots in every row;
    • the bar number clearing the bracket.
  • IncrementalResize: numbered voices and chord slots. Each resize step equals a fresh render.
  • ScaleToWidthCallCount: one call per renderer and cycle for numbered staves, in page and horizontal layout.
  • Visual tests:
    • new: numbered-chords, numbered-chords-grace-dead, numbered-chords-tuplets, numbered-chord-ties, numbered-voices, numbered-voices-sparse, numbered-voices-4, numbered-single-note;
    • updated: numbered, numbered-durations, numbered-tuplets, numbered-whole-rests, general/colors, colors-disabled, colors-voice-cascade, tremolo-flags-mixed.
  • alphaTex import and export round trip of notes.
  • Results: TS 2430/2430, C# 2255/2255, Kotlin 2255/2255.
  • Allocations: no measurable increase for single-voice numbered staves against develop. Measured with V8's sampling heap profiler on numbered.gp.

Checklist

  • I consent that this change becomes part of alphaTab under its current or any future open source license
  • This PR is linked to an accepted issue (see above)
  • Changes are implemented
  • New tests were added

AI authorship disclosure

  • No AI agent authored any part of this PR (description, code, tests, or commit messages)
  • An AI agent contributed to this PR. The AI-authored disclosure block
    (alphatab-ai-authored-v1) is present at the top of this body, and I have
    personally reviewed every change and can explain each one

Further details

  • This is a breaking change
  • Docs / release notes updated in website/

Numbered (jianpu) staves write every chord note and every voice instead of a
single number of the first voice.

- Chords stack their numbers upwards from the line of single numbers. Every
  number keeps its own octave dots, accidental and augmentation dot; underlines
  and dashes belong to the lowest number.
- Every voice is written on its own row, voice 1 on top, aligned in time. A
  system without further voices keeps a single row; an empty first voice keeps
  its blank row. Further rows use the secondary voice colour, their time
  signature copy included.
- The numbers sit on half-line steps of the renderer's line grid. Each bar
  collects what its chords and rows need; the first bar of a staff merges the
  needs of the system when the bars are scaled, and every bar moves its numbers
  onto the shared steps, so chord numbers and rows line up along a system.
- Rows are spaced by their content: what hangs below the upper row, two staff
  spaces, and what rises above the lower row, paired per bar. Time signature
  copies count in the bars that write them.
- The time signature and repeat dots are written in front of every row.
- Ties are drawn per note, effect slurs once per chord.
- Low octave dots go below the duration underlines; the underline spanning a
  group sits directly below the numbers.
- NumberedStaffConfig.notes with NumberedNoteMode (AllNotes, SingleNote) and
  the alphaTex property `notes`; SingleNote writes the highest note of voice 1.
- Line renderers expose staff rows for bar-level glyphs (time signatures,
  repeat dots, bar lines, dead-slapped crosses).
- The horizontal layout scales the bars of its system once it holds all of
  them, as the page layouts do. The staff bottom is reset and registered with
  the staff overflows.

Refs #2437
@Danielku15
Danielku15 merged commit 17793fd into develop Nov 7, 2026
12 checks passed
@Danielku15
Danielku15 deleted the feat/numbered-chords-voices branch November 7, 2026 21:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Numbered Music Notation - Chords and Multivoice

1 participant