Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions packages/alphatab/src/rendering/glyphs/TabBeatContainerGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,56 @@ export class TabBeatContainerGlyph extends BeatContainerGlyph {
const tapSlur: TabTieGlyph = new TabTieGlyph(`tab.tie.leftHandTap.${n.id}`, n, n, false);
this.addTie(tapSlur);
}
// start effect slur on first beat
if (n.isEffectSlurOrigin && n.effectSlurDestination) {
// H/P arc start-side: create individual arc per hammer-pull pair
if (n.isHammerPullOrigin && n.hammerPullDestination) {
const dest = n.hammerPullDestination;
const slurText = dest.fret >= n.fret ? 'H' : 'P';
let expanded: boolean = false;
for (const slur of this._effectSlurs) {
if (slur.tryExpand(n, dest, false, false, slurText)) {
expanded = true;
break;
}
}
if (!expanded) {
const effectSlur: TabSlurGlyph = new TabSlurGlyph(
`tab.slur.effect.${n.id}`,
n,
dest,
false,
false,
slurText
);
this._effectSlurs.push(effectSlur);
this.addTie(effectSlur);
}
}
// H/P arc end-side: for cross-bar rendering
if (n.isHammerPullDestination && n.hammerPullOrigin) {
const origin = n.hammerPullOrigin;
const slurText = n.fret >= origin.fret ? 'H' : 'P';
let expanded: boolean = false;
for (const slur of this._effectSlurs) {
if (slur.tryExpand(origin, n, false, true, slurText)) {
expanded = true;
break;
}
}
if (!expanded) {
const effectSlur: TabSlurGlyph = new TabSlurGlyph(
`tab.slur.effect.${origin.id}`,
origin,
n,
false,
true,
slurText
);
this._effectSlurs.push(effectSlur);
this.addTie(effectSlur);
}
}
// start non-H/P effect slur (e.g. legato slide)
if (n.isEffectSlurOrigin && n.effectSlurDestination && !n.isHammerPullOrigin) {
let expanded: boolean = false;
for (const slur of this._effectSlurs) {
if (slur.tryExpand(n, n.effectSlurDestination, false, false)) {
Expand All @@ -77,8 +125,8 @@ export class TabBeatContainerGlyph extends BeatContainerGlyph {
this.addTie(effectSlur);
}
}
// end effect slur on last beat
if (n.isEffectSlurDestination && n.effectSlurOrigin) {
// end non-H/P effect slur
if (n.isEffectSlurDestination && n.effectSlurOrigin && !n.isHammerPullDestination) {
let expanded: boolean = false;
for (const slur of this._effectSlurs) {
if (slur.tryExpand(n.effectSlurOrigin, n, false, true)) {
Expand Down
14 changes: 12 additions & 2 deletions packages/alphatab/src/rendering/glyphs/TabSlurGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,27 @@ import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection
*/
export class TabSlurGlyph extends TabTieGlyph {
private _forSlide: boolean;
private readonly _slurText?: string;

public constructor(slurEffectId: string, startNote: Note, endNote: Note, forSlide: boolean, forEnd:boolean) {
public constructor(slurEffectId: string, startNote: Note, endNote: Note, forSlide: boolean, forEnd:boolean, slurText?: string) {
super(slurEffectId, startNote, endNote, forEnd);
this._forSlide = forSlide;
this._slurText = slurText;
}

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

public tryExpand(startNote: Note, endNote: Note, forSlide: boolean, forEnd: boolean): boolean {
protected override getSlurText(): string | undefined {
return this._slurText;
}

public tryExpand(startNote: Note, endNote: Note, forSlide: boolean, forEnd: boolean, slurText?: string): boolean {
// same label required (when provided)
if (slurText !== undefined && this._slurText !== slurText) {
return false;
}
// same type required
if (this._forSlide !== forSlide) {
return false;
Expand Down
26 changes: 24 additions & 2 deletions packages/alphatab/src/rendering/glyphs/TieGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,14 +165,16 @@ export abstract class TieGlyph extends Glyph implements ITieGlyph {
return;
}

const isDown = this.tieDirection === BeamDirection.Down;

if (this.shouldDrawBendSlur()) {
TieGlyph.drawBendSlur(
canvas,
cx + this._startX,
cy + this._startY,
cx + this._endX,
cy + this._endY,
this.tieDirection === BeamDirection.Down,
isDown,
this.renderer.smuflMetrics.tieHeight
);
} else {
Expand All @@ -183,11 +185,31 @@ export abstract class TieGlyph extends Glyph implements ITieGlyph {
cy + this._startY,
cx + this._endX,
cy + this._endY,
this.tieDirection === BeamDirection.Down,
isDown,
this._tieHeight,
this.renderer.smuflMetrics.tieMidpointThickness
);
}

const slurText = this.getSlurText();
if (slurText) {
const midX = cx + (this._startX + this._endX) / 2;
const midY = cy + (this._startY + this._endY) / 2;
const apexOffset = this._tieHeight * 0.75;
const apexY = midY + (isDown ? apexOffset : -apexOffset);
const w = canvas.measureText(slurText).width;
const fontSize = canvas.font.size;
// text above: fontSize already includes descender space below the baseline,
// providing natural padding for capital letters like H/P
const textY = isDown
? apexY + fontSize * 0.3
: apexY - fontSize * 1.05;
canvas.fillText(slurText, midX - w / 2, textY);
}
}

protected getSlurText(): string | undefined {
return undefined;
}

protected abstract shouldDrawBendSlur(): boolean;
Expand Down