Skip to content

Commit ee5fd63

Browse files
committed
feat(tab): add H/P text labels to hammer-on and pull-off arcs
In the Tab renderer, the arc connecting hammer-on and pull-off notes is now annotated with an "H" (ascending fret = hammer-on) or "P" (descending fret = pull-off) label above the arc midpoint. The label is drawn via an overridden paint() in TabSlurGlyph, reusing the same canvas.fillText path already used for whammy/bend slurText. TieGlyph's coordinate fields (_startX/Y, _endX/Y, _tieHeight, _shouldPaint) are widened from private to protected to allow the subclass to read them during paint. Fixes #2608
1 parent 220858a commit ee5fd63

3 files changed

Lines changed: 30 additions & 8 deletions

File tree

packages/alphatab/src/rendering/glyphs/TabBeatContainerGlyph.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,17 @@ export class TabBeatContainerGlyph extends BeatContainerGlyph {
6666
}
6767
}
6868
if (!expanded) {
69+
let slurText: string | undefined = undefined;
70+
if (n.isHammerPullOrigin && n.hammerPullDestination) {
71+
slurText = n.hammerPullDestination.fret >= n.fret ? 'H' : 'P';
72+
}
6973
const effectSlur: TabSlurGlyph = new TabSlurGlyph(
7074
`tab.slur.effect.${n.id}`,
7175
n,
7276
n.effectSlurDestination,
7377
false,
74-
false
78+
false,
79+
slurText
7580
);
7681
this._effectSlurs.push(effectSlur);
7782
this.addTie(effectSlur);

packages/alphatab/src/rendering/glyphs/TabSlurGlyph.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Note } from '@coderline/alphatab/model/Note';
2+
import type { ICanvas } from '@coderline/alphatab/platform/ICanvas';
23
import { TabTieGlyph } from '@coderline/alphatab/rendering/glyphs/TabTieGlyph';
34
import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection';
45

@@ -7,16 +8,32 @@ import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection
78
*/
89
export class TabSlurGlyph extends TabTieGlyph {
910
private _forSlide: boolean;
11+
private readonly _slurText?: string;
1012

11-
public constructor(slurEffectId: string, startNote: Note, endNote: Note, forSlide: boolean, forEnd:boolean) {
13+
public constructor(slurEffectId: string, startNote: Note, endNote: Note, forSlide: boolean, forEnd:boolean, slurText?: string) {
1214
super(slurEffectId, startNote, endNote, forEnd);
1315
this._forSlide = forSlide;
16+
this._slurText = slurText;
1417
}
1518

1619
public override getTieHeight(startX: number, _startY: number, endX: number, _endY: number): number {
1720
return (Math.log(endX - startX + 1) * this.renderer.settings.notation.slurHeight) / 2;
1821
}
1922

23+
public override paint(cx: number, cy: number, canvas: ICanvas): void {
24+
super.paint(cx, cy, canvas);
25+
if (this._slurText && this._shouldPaint) {
26+
const midX = cx + (this._startX + this._endX) / 2;
27+
const midY = cy + (this._startY + this._endY) / 2;
28+
const apexOffset = this._tieHeight * 0.75;
29+
const isDown = this.tieDirection === BeamDirection.Down;
30+
const apexY = midY + (isDown ? apexOffset : -apexOffset);
31+
const w = canvas.measureText(this._slurText).width;
32+
const textY = isDown ? apexY : apexY - canvas.font.size;
33+
canvas.fillText(this._slurText, midX - w / 2, textY);
34+
}
35+
}
36+
2037
public tryExpand(startNote: Note, endNote: Note, forSlide: boolean, forEnd: boolean): boolean {
2138
// same type required
2239
if (this._forSlide !== forSlide) {

packages/alphatab/src/rendering/glyphs/TieGlyph.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ export abstract class TieGlyph extends Glyph implements ITieGlyph {
3131
this.isForEnd = forEnd;
3232
}
3333

34-
private _startX: number = 0;
35-
private _startY: number = 0;
36-
private _endX: number = 0;
37-
private _endY: number = 0;
38-
private _tieHeight: number = 0;
34+
protected _startX: number = 0;
35+
protected _startY: number = 0;
36+
protected _endX: number = 0;
37+
protected _endY: number = 0;
38+
protected _tieHeight: number = 0;
3939
private _boundingBox?: Bounds;
40-
private _shouldPaint: boolean = false;
40+
protected _shouldPaint: boolean = false;
4141

4242
public get checkForOverflow() {
4343
return this._shouldPaint && this._boundingBox !== undefined;

0 commit comments

Comments
 (0)