Skip to content

feat(rendering): add per-beat rest display position via beat.restDisplayTone, beat.restDisplayOctave#2721

Open
roscopeeco wants to merge 1 commit into
CoderLine:developfrom
roscopeeco:feature/rest-position-v2
Open

feat(rendering): add per-beat rest display position via beat.restDisplayTone, beat.restDisplayOctave#2721
roscopeeco wants to merge 1 commit into
CoderLine:developfrom
roscopeeco:feature/rest-position-v2

Conversation

@roscopeeco

Copy link
Copy Markdown

Adds Beat.restDisplayTone (Tone enum) and Beat.restDisplayOctave to allow specifying the vertical position of a rest on the staff on a per-beat basis. The AlphaTex restdisplaypitch tag (e.g. r.4{restdisplaypitch E4}) sets these values. AccidentalHelper.calculateRestDisplaySteps converts the pitch to rendering steps, aligned to match the legacy RestPosition enum spacing (+0.5).

Includes 77 visual tests covering treble, bass, alto and tenor clefs across staff positions, durations, multi-voice scenarios, and variable line counts.

This PR only includes TypeScript changes

Issues

Fixes #

Proposed changes

Checklist

  • [ X] I consent that this change becomes part of alphaTab under it's current or any future open source license
  • Changes are implemented
  • [ X] New tests were added

Further details

  • This is a breaking change
  • [ X] This change will require update of the documentation/website

return ApplyNodeResult.Applied;
case 'restdisplaypitch': {
const pitchText = (p.arguments!.arguments[0] as AlphaTexTextNode).text.toUpperCase();
const toneChar = pitchText[0] as keyof typeof Tone;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code cannot be transpiled to C# and Kotlin.
You can reuse alphaTab.model.Tuning to parse such strings

Comment thread packages/alphatab/src/model/Tone.ts Outdated
Comment on lines +6 to +14
export enum Tone {
C = 0,
D = 2,
E = 4,
F = 5,
G = 7,
A = 9,
B = 11
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea with the enum is good, but it is in conflict with how alphaTab handles tones at all other places. I'd prefer consistency (using raw numnber properties) until we can introduce such an enum consistently at all places.

}

const spelling = ModelUtils.resolveSpelling(bar.keySignature, noteValue, NoteAccidentalMode.Default);
return AccidentalHelper.calculateNoteSteps(bar.clef, spelling) + 0.5;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ 0.5 seems like a workaround indicating to a problem elsewhere. Can we eliminate or move this to the actual place where things where the need for the workaround gets clear?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue of adding 0.5 is needed because the glyph baseline data contained in bravura_metadata.json varies per glyph.
For example, the black note head is perfectly centred vertically [0.5, -0.5] , but the rest glyphs all have different off-centre base lines.

The 0.5 is used to calculate the perfect positioning of the half note rest, so everything is adjusted by 0.5,
but visually the 0.5 aligns the half note perfectly.

Do we want all rests, quarter note, eighth note, 16th note, 32nd etc. notes to be aligned perfectly centred visually on the Y axis,either on the line or in the space, or is it just the half note that needs to be visually perfect?

If all rests need to be visually perfectly centered, then we'd need to include the Bravura metadata coordinates in the
calculation. Creating a helper function to do this.

We could call this after calling calculateRestDisplaySteps and add the result, the return value, to replace the 0.5 hard coded value.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Danielku15 any thoughts on this? Also, the other three changes have been made.

@Danielku15 Danielku15 Jun 14, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue of adding 0.5 is needed because the glyph baseline data contained in bravura_metadata.json varies per glyph.

This is on purpose. SMuFL specifies that the baseline y=0 should be where the staffline is.

Also for noteheads the baseline indicates the staffline.

Do we want all rests, quarter note, eighth note, 16th note, 32nd etc. notes to be aligned perfectly centred visually on the Y axis,either on the line or in the space, or is it just the half note that needs to be visually perfect?

if we have a C4 note, and manually position a rest at C4. the rest glyph will be placed on the same staff-line following the common rest placement and offsets as usual.

calculateRestDisplaySteps should not divert from the general placement logic. adding 0.5 seem to be the wrong choice for this method.

What indeed could be is that some legacy/tech-debt areas in alphaTab interfere with the placement. If that's the case I can have a deeper look at the surroundings. In case of doubts you could always craft a MusicXML file and open it in MusicXML to check the placement logic.

A MusicXML example where we place rests at the same pitch as notes.

image
<note default-x="80.21" default-y="-50">
  <pitch>
    <step>C</step>
    <octave>4</octave>
    </pitch>
  <duration>1</duration>
  <voice>1</voice>
  <type>quarter</type>
  <stem>up</stem>
</note>
<note default-x="117.3" default-y="-20">
  <rest>
    <display-step>C</display-step>
    <display-octave>4</display-octave>
  </rest>
  <duration>1</duration>
  <voice>1</voice>
  <type>quarter</type>
</note>
<note default-x="154.4" default-y="10">
  <pitch>
    <step>A</step>
    <octave>5</octave>
    </pitch>
  <duration>1</duration>
  <voice>1</voice>
  <type>quarter</type>
  <stem>down</stem>
</note>
<note default-x="191.49" default-y="-20">
  <rest>
    <display-step>A</display-step>
    <display-octave>5</display-octave>
  </rest>
  <duration>1</duration>
  <voice>1</voice>
  <type>quarter</type>
</note>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, as I stated previously, the reason for the offset is to handle the half note rest, which visually does not look correct. If that adjustment is not made, it may not be visually perceptible for some of the other rests but for a half note, it does not position correctly with the standard calculation.

See the test below for half note positioning as is:
packages/alphatab/test-data/visual-tests/rest-position/alto/rest-position-multi-voice-secondary-only-F3-line-1.png

My solution would be to implement a per rest adjustment value to compensate for the Smuffle glypths off-center placement that would be applied in calculateRestDisplaySteps.

If no adjustment is made then the half note and possibly the whole note rest will look incorrect.

I don't think there's anything wrong with your calculations in alphaTab. It's just an artifact of the off center positioning of the glyphs.

I'll leave it to you to determine what needs to be done,

Comment on lines +4 to +15
type ClefData = {
tex: string;
filler: string;
positions: [string, string][];
primaryFiller: string;
secondaryFiller: string;
multiVoicePrimary: [string, string];
multiVoiceSecondary: [string, string];
};

// Staff positions from bottom to top for each clef: [pitch, label]
const clefs: Record<string, ClefData> = {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write only code which is safe for transpilation.

https://alphatab.net/docs/contributing#toolchain

…tch tag

Adds Beat.restDisplayTone (Tone enum) and Beat.restDisplayOctave to allow
specifying the vertical position of a rest on the staff on a per-beat basis.
The AlphaTex restdisplaypitch tag (e.g. r.4{restdisplaypitch E4}) sets these
values. AccidentalHelper.calculateRestDisplaySteps converts the pitch to
rendering steps, aligned to match the legacy RestPosition enum spacing (+0.5).

Includes 77 visual tests covering treble, bass, alto and tenor clefs across
all staff positions, durations, multi-voice scenarios, and variable line counts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@roscopeeco roscopeeco force-pushed the feature/rest-position-v2 branch from 9625031 to 146eb34 Compare June 1, 2026 18:01
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.

2 participants