Skip to content

alphaTex: cross-voice slur is paired in file order, so it is read backwards and the renderer crashes or draws NaN paths #2892

Description

@Geoff-Walker

Version: 1.8.3 (also reproduced on 1.8.4)

Summary

alphaTex pairs {slur <id>} in file order, not time order. Every voice of a staff is written as a block, so a slur that starts in a later-written voice and ends on a note in an earlier-written voice is read backwards: its "origin" sits in a later bar than its "destination". This shape is common in piano music, where a slur runs from an inner voice up into the top voice. The renderer then:

  • throws when the reversed slur crosses a line break, with either
    • TypeError: Cannot read properties of undefined (reading 'barRenderers') in TieGlyph.doLayout (called from ScoreBarRenderer._finalizeTies), or
    • TypeError: Cannot read properties of null (reading 'getBeatDirection') in ScoreSlurGlyph.calculateTieDirection
  • otherwise draws a path with NaN control points (nothing sensible appears)

Whether it throws depends on where line breaks fall, so the same score can render at one width and crash at another.

Minimal repro (alphaTex)

\track "P"
\staff {score} \tuning piano \clef G2 \ts (4 4)
\voice
:1 c4 |
:1 e4{slur a} |
\voice
:1 g4{slur a} |
:1 r |

The slur is meant to run from the voice-2 G4 in bar 1 to the voice-1 E4 in bar 2.

  • ScoreRenderer (SVG engine, enableLazyLoading = false), width 300: throws ...reading 'barRenderers'.
  • Width 600 and wider: renders, but the slur path contains NaN.

It also breaks a round trip through alphaTab's own exporter

A MusicXML file with the same cross-voice slur, loaded with ScoreLoader, renders correctly. Passing it through AlphaTexExporter then AlphaTexImporter and rendering reproduces the crash above. So alphaTex, as exported by alphaTab, cannot currently carry this slur.

A workaround that confirms the cause

After readScore(), walk the notes. For any note whose slurDestination is earlier (bar index, then displayStart) than the note itself, swap origin and destination, then call score.finish(settings). With only that change:

  • the repro renders at every width with no NaN;
  • on a corpus of 744 converted classical piano movements, clean renders at four widths (600–1600 px) went from 543 to 711.

Two possible fixes:

  • the alphaTex importer orders each slur pair by position after reading; or
  • the syntax gains a way to mark which note begins the slur.

In either case, calculateTieDirection should not assume lookupStartBeatRenderer() is non-null. From reading develop, #2800 changes the line-break branch of TieGlyph.doLayout but leaves that call as it is; I have not run develop to confirm.

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