Skip to content

AlphaTex export → import round-trip fails when a note's harmonicValue is fractional (ah / fh) #2896

Description

@stewacide

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.

Is there an existing issue for this?

  • I have searched the existing issues

I have read the contribution rules

AI authorship

AI-assisted (disclosure block included as described in AGENTS.md)

Current Behavior

An AlphaTex export → import round-trip fails when a note carries a fractional harmonicValue.

AlphaTexExporter.exportToString() writes that value into the note's effect group as a decimal — e.g. {fh 2.4} — and ScoreLoader.loadAlphaTex() then rejects that same output:

AT206  Unexpected end of file. Group not closed.
AT202  Unexpected 'RBrace' token. Expected one of following: Number,String,Ident

Whole-number values are fine. Only fractional ones fail. Measured on v1.8.4, starting from a score with all other harmonics cleared so nothing else could influence the result:

harmonicType harmonicValue exported re-import
Feedback (6) (none) — ok
Feedback (6) 0 {fh 0} ok
Feedback (6) 2 {fh 2} ok
Feedback (6) 12 {fh 12} ok
Feedback (6) 2.4 {fh 2.4} throws
Feedback (6) 3.7 {fh 3.7} throws
Artificial (2) 2 {ah 2} ok
Artificial (2) 2.4 {ah 2.4} throws

The failure does not require a file at all — appending the effect to an otherwise valid AlphaTex document reproduces it, so this is not specific to any particular score:

appended before EOF result
1.1{fh} ok
1.1{fh 2} ok
1.1{fh 2.4} throws
1.1{ah} ok
1.1{ah 2} ok
1.1{ah 2.4} throws

We hit this in production: we export Guitar Pro scores to AlphaTex to serve from a song catalog, and every affected score published and listed normally but then failed to load for the client. A score whose harmonicValue is 2.4 in the source .gp is unusable as published.

Expected Behavior

Whatever AlphaTexExporter writes should be readable by ScoreLoader.loadAlphaTex — i.e. export → import should round-trip losslessly, including fractional harmonic values. A decimal literal that the exporter itself just produced should not be a parse error on the way back in.

If fractional harmonic values are not meant to be representable, the exporter should not silently emit them; either way the current pair of behaviours (emit, then reject) is not self-consistent.

Steps To Reproduce

  1. Load any Guitar Pro file into alphaTab (ScoreLoader.loadScoreFromBytes).
  2. On one note, set harmonicType to 6 (Feedback) or 2 (Artificial) and set harmonicValue to a fraction such as 2.4. Leave every other note's harmonic cleared.
  3. Export: const tex = new alphaTab.exporter.AlphaTexExporter().exportToString(score, new alphaTab.Settings()).
  4. Search the output for fh / ah — you will find the decimal you set, e.g. {fh 2.4}.
  5. Re-import that same string: ScoreLoader.loadAlphaTex(tex, new alphaTab.Settings()).
  6. It throws AT206 / AT202 as shown above.

Repeat with harmonicValue set to 2 instead of 2.4 and step 6 succeeds, which is how we isolated the fractional part as the trigger.

Version and Environment

[AlphaTab][VersionInfo] alphaTab 1.8.4
[AlphaTab][VersionInfo] commit: 022a45c8e42370f9e12e68949d11eada370da83d
[AlphaTab][VersionInfo] build date: 2026-07-05T14:46:18.224Z
[AlphaTab][VersionInfo] High DPI: 1
[AlphaTab][VersionInfo] Platform: NodeJs
[AlphaTab][VersionInfo] WebPack: false
[AlphaTab][VersionInfo] Vite: false

All rows in the tables above were produced against an unmodified v1.8.4 build (verified free of any local patches) on Node.js v24.18.0, macOS.

Platform

Node.js

Anything else?

  • We only exercised ah (Artificial) and fh (Feedback). th (Tap), ph (Pinch) and sh (Semi) appear to take the same value argument in the same note-effect group and are likely affected too, but we did not confirm that and did not want to overstate it — happy to test more if useful.
  • We could not construct a small standalone AlphaTex document that isolates this: the minimal forms we tried were themselves rejected before the fractional value came into play. So the reproduction above is programmatic rather than a copy-pasteable document.
  • We are currently working around this in our own vendored copy of v1.8.4 by accepting these values locally. We have not proposed a code change here and are happy to run any additional experiments you need — for example exporting a known-good score at several fractional values across every valued harmonic type, or checking whether the same asymmetry shows up in the .NET and Kotlin ports, since TypeScript is the source of truth for those.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    Priority

    None yet

    Area

    None yet

    Platform

    None yet

    Work State

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions