Skip to content

feat: keep the sharp or flat spelling of tunings - #3020

Merged
Danielku15 merged 20 commits into
developfrom
feature/issue-2802
Nov 8, 2026
Merged

Danielku15 merged 20 commits into
developfrom
feature/issue-2802

Conversation

@Danielku15

@Danielku15 Danielku15 commented Nov 8, 2026 •

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 #2802

Proposed changes

Tunings keep the sharp or flat spelling of every string (F#3 vs Gb3) from import to rendering and export. The tuning display shows the accidentals as ♯ and ♭.

One representation of a written pitch

  • The new public SpelledPitch is an immutable value: step, alter and octave, plus the derived MIDI value.
  • It replaces the internal TuningParseResult and ResolvedSpelling types and their duplicated lookup tables. ModelUtils.resolveSpelling and the new ModelUtils.parsePitch (same acceptance rules as before) return it.
  • resolveSpelling no longer allocates closures for its candidate lookups.

Tuning model

  • Tuning.pitches is the stored tuning.
  • Tuning.tunings is derived from it; setting it spells the strings with flats, as before.
  • The score JSON writes pitches as text ("D#4") and still reads the tunings values of earlier versions (@json_read_only).
  • The serializer generator now supports arrays of @json_immutable values.

Formats

  • alphaTex: \tuning keeps the spelling as written and exports it back.
  • Guitar Pro:
    • A tuning with the Flat element is spelled with flats, all others with sharps. This was checked against Guitar Pro 8 files saved with sharp and with flat display.
    • The exporter writes flats unless every string with an accidental is spelled sharp, because Guitar Pro has only one choice per tuning.
    • Guitar Pro 6 files never contain the element, so they show sharps.
  • MusicXML: tuning-step and tuning-alter give the spelling.

Rendering

  • The score tuning and the staff tuning draw the names as text with ♯ ♭ 𝄪 𝄫.
  • Natural tunings render unchanged.
  • This needed alphaSkia 4.0.157, which aligns text mixing several fonts like the HTML5 canvas. Before, a fallback glyph (here from Noto Music) moved the whole line down.

Other

  • Kotlin runtime: indexed DoubleList.map and List.slice(start, end).
  • Number.isInteger in the C# and Kotlin runtimes.
  • C# test runtime: Expect(Action, string).
  • Tuning.noteNames, getTextForTuning and getTextPartsForTuning are deprecated and unchanged.

Behaviour notes (also in the release notes)

  • Changing the array returned by Tuning.tunings in place no longer changes the tuning; assign a new array instead.
  • The score JSON contains pitches instead of tunings.
  • Guitar Pro tunings with accidentals and no flat flag, including all Guitar Pro 6 files, show sharps instead of flats.

Reference images

  • Tuning references with accidentals changed.
  • The alphaSkia update changed several MusicXML and general references.
  • alphaSkia 4's FreeType mode falls back to fonts installed on the machine. The visual tests therefore register Noto Sans SC (CJK texts) and Noto Sans Math (symbols like arrows and ⍉) and list them in the font families. All characters the tests draw now come from fonts in the repository.

Tests

  • New tests for SpelledPitch, the tuning pitches and their JSON form, alphaTex, Guitar Pro 6/8 and MusicXML import and export, and the round trips.
  • Visual tests for the score and staff tuning with accidentals and for the Guitar Pro test file.
  • Local runs: TypeScript 2527/2527, C# 2352/2352, Kotlin 2352/2352.

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
  • I have read AGENTS.md if an AI helped draft any part of this PR

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/

Introduces the public SpelledPitch (step, alter, octave) as the single
representation of a written pitch and uses it for parsing and spelling
resolution:

- ResolvedSpelling is replaced by SpelledPitch, resolveSpelling returns it.
- TuningParseResult/TuningParseResultTone are replaced by PitchParseResult
  returned by ModelUtils.parsePitch with unchanged acceptance rules. The
  Note.tone/Note.octave values derived from it are unchanged.
- Duplicated step and accidental tables are folded into SpelledPitch.
- resolveSpelling no longer allocates closures for candidate lookups.
- Tuning.noteNames, getTextForTuning and getTextPartsForTuning are
  deprecated with unchanged behaviour.

Refs #2802
Tuning.spellings holds the written pitch of every string next to the
tuning values, null when the spelling is unknown. Tuning.getSpelling
returns the stored pitch while it matches the tuning value and the flat
spelling otherwise, so code changing the tuning values keeps working.

The serializer generator now emits a non-null assertion when reading a
nullable array of own types without add method.

Refs #2802
\tuning stores the written pitch of every string, the exporter writes the
tunings with their spelling. Tunings without known spelling are exported
with flats as before.

Refs #2802
staff-tuning keeps tuning-step and tuning-alter as the written pitch of
the string. Fractional alters keep their tuning value without spelling.

Refs #2802
The score tuning and the inline tuning show the written spelling of every
string with the accidental drawn from the music font next to the step
letter. EngravingSettings.tuningGlyphAccidentalScale controls the size of
the accidentals.

Refs #2802
- Kotlin: indexed DoubleList.map and List.slice(start, end).
- C# and Kotlin: Number.isInteger.
- The MusicXML tuning parsing avoids optional chaining across several
  members which Kotlin does not short-circuit.

Refs #2802
…itar Pro

Guitar Pro 7 and 8 store one sharp or flat choice per tuning: the Flat
element of the tuning and the TuningFlat property are written for flats
and omitted for sharps. The importer spells the strings accordingly, the
exporter writes sharps if every string with an accidental is spelled with
a sharp. Guitar Pro 6 files have no such choice and keep the flat display.

Refs #2802
Arrays of @json_immutable types are read with fromJson and written with
toJson per item.

Refs #2802
- Tuning.pitches is the stored tuning. Tuning.tunings is derived from it
  and spells the strings with flats when set. JSON stores the pitches as
  text like "D#4" and still reads the tunings of earlier versions.
- SpelledPitch is an immutable value.
- Guitar Pro tunings are spelled with sharps unless the tuning has the
  Flat element, without distinguishing the format version.
- The tuning display writes the accidentals as text characters instead
  of placing music font symbols, the tuningGlyphAccidentalScale setting
  is removed.
- C# tests can expect exceptions with a message.

Refs #2802
color-performance compares the colored and the uncolored reference on
every render and fails after all renders, so a mismatch of the colored
reference no longer hides the uncolored one.
The visual tests register Noto Sans Math and Noto Sans SC and add them to
the font families, so the CJK texts and symbols like arrows no longer
fall back to fonts installed on the machine.
@Danielku15
Danielku15 merged commit ec751a2 into develop Nov 8, 2026
12 checks passed
@Danielku15
Danielku15 deleted the feature/issue-2802 branch November 8, 2026 21:12
@Danielku15 Danielku15 mentioned this pull request Nov 8, 2026
7 of 9 tasks
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.

Support tuning alterations

1 participant