Skip to content
Merged
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
26 changes: 25 additions & 1 deletion packages/alphatab/src/rendering/glyphs/BeatOnNoteGlyphBase.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Duration } from '@coderline/alphatab/model/Duration';
import type { Note } from '@coderline/alphatab/model/Note';
import type { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase';
import { type NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase';
import { BeatGlyphBase } from '@coderline/alphatab/rendering/glyphs/BeatGlyphBase';
import type { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph';
import type { BeatBounds } from '@coderline/alphatab/rendering/utils/BeatBounds';

/**
Expand All @@ -17,4 +19,26 @@ export abstract class BeatOnNoteGlyphBase extends BeatGlyphBase {
public abstract getRestY(requestedPosition: NoteYPosition): number;
public abstract getHighestNoteY(requestedPosition: NoteYPosition): number;
public abstract getLowestNoteY(requestedPosition: NoteYPosition): number;

/**
* Resolves the requested position on the given rest glyph.
* Rests have no stems, positions with stems reserve the space of a quarter note stem (e.g. for beams passing by).
*/
protected getRestGlyphY(g: Glyph | null, requestedPosition: NoteYPosition): number {
if (!g) {
return 0;
}
switch (requestedPosition) {
case NoteYPosition.TopWithStem:
return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
case NoteYPosition.Top:
return g.getBoundingBoxTop();
case NoteYPosition.Bottom:
return g.getBoundingBoxBottom();
case NoteYPosition.BottomWithStem:
return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
}
// center and stem positions
return g.getBoundingBoxTop() + g.height / 2;
}
}
54 changes: 52 additions & 2 deletions packages/alphatab/src/rendering/glyphs/NoteHeadGlyph.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { EngravingSettings } from '@coderline/alphatab/EngravingSettings';
import { Duration } from '@coderline/alphatab/model/Duration';
import { MusicFontGlyph } from '@coderline/alphatab/rendering/glyphs/MusicFontGlyph';
import { MusicFontSymbol } from '@coderline/alphatab/model/MusicFontSymbol';
import type { ICanvas } from '@coderline/alphatab/platform/ICanvas';
import { EngravingSettings } from '@coderline/alphatab/EngravingSettings';
import { NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase';
import { MusicFontGlyph } from '@coderline/alphatab/rendering/glyphs/MusicFontGlyph';
import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection';

/**
* @internal
Expand All @@ -19,6 +21,54 @@ export class NoteHeadGlyphBase extends MusicFontGlyph {
}
super.paint(cx, cy, canvas);
}

/**
* Gets the x-offset from the left of the note head at which a stem in the given direction attaches.
* @remarks The metrics are passed explicitly as positions are also requested before the note head is laid out.
*/
public getStemX(smufl: EngravingSettings, direction: BeamDirection): number {
if (direction === BeamDirection.Up) {
// stem-up attaches on the right side
return (
(smufl.stemUp.has(this.symbol)
? smufl.stemUp.get(this.symbol)!.x
: smufl.glyphWidths.get(this.symbol)!) * this.glyphScale
);
}
// stem-down attaches on the left side
return (smufl.stemDown.has(this.symbol) ? smufl.stemDown.get(this.symbol)!.x : 0) * this.glyphScale;
}

/**
* Gets the y-position of the note head at the given position (in the same coordinate space as {@link y}).
* Positions depending on the stem length cannot be resolved by the note head and
* are treated like {@link NoteYPosition.Top} and {@link NoteYPosition.Bottom}.
* @remarks The metrics are passed explicitly as positions are also requested before the note head is laid out.
*/
public getNoteHeadY(smufl: EngravingSettings, requestedPosition: NoteYPosition): number {
switch (requestedPosition) {
case NoteYPosition.TopWithStem:
case NoteYPosition.Top:
return this.y - this.height / 2;
case NoteYPosition.BottomWithStem:
case NoteYPosition.Bottom:
return this.y + this.height / 2;
case NoteYPosition.StemUp:
return (
this.y -
(smufl.stemUp.has(this.symbol) ? smufl.stemUp.get(this.symbol)!.bottomY : 0) * this.glyphScale
);
case NoteYPosition.StemDown:
return (
this.y -
(smufl.stemDown.has(this.symbol)
? smufl.stemDown.get(this.symbol)!.topY
: -smufl.glyphHeights.get(this.symbol)! / 2) *
this.glyphScale
);
}
return this.y;
}
}

/**
Expand Down
19 changes: 1 addition & 18 deletions packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,24 +114,7 @@ export class ScoreBeatGlyph extends BeatOnNoteGlyphBase {
}

public override getRestY(requestedPosition: NoteYPosition): number {
const g = this.restGlyph;
if (g) {
switch (requestedPosition) {
case NoteYPosition.TopWithStem:
return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
case NoteYPosition.Top:
return g.getBoundingBoxTop();
case NoteYPosition.Center:
case NoteYPosition.StemUp:
case NoteYPosition.StemDown:
return g.getBoundingBoxTop() + g.height / 2;
case NoteYPosition.Bottom:
return g.getBoundingBoxBottom();
case NoteYPosition.BottomWithStem:
return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true);
}
}
return 0;
return this.getRestGlyphY(this.restGlyph, requestedPosition);
}

public applyRestCollisionOffset() {
Expand Down
53 changes: 12 additions & 41 deletions packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type { ICanvas } from '@coderline/alphatab/platform/ICanvas';
import { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase';
import { DeadSlappedBeatGlyph } from '@coderline/alphatab/rendering/glyphs/DeadSlappedBeatGlyph';
import type { EffectGlyph } from '@coderline/alphatab/rendering/glyphs/EffectGlyph';
import type { MusicFontGlyph } from '@coderline/alphatab/rendering/glyphs/MusicFontGlyph';
import type { NoteHeadGlyphBase } from '@coderline/alphatab/rendering/glyphs/NoteHeadGlyph';
import {
ScoreChordNoteHeadInfo,
Expand All @@ -25,7 +24,7 @@ import { NoteBounds } from '@coderline/alphatab/rendering/utils/NoteBounds';
* @internal
*/
export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
private _noteGlyphLookup: Map<number, MusicFontGlyph> = new Map();
private _noteGlyphLookup: Map<number, NoteHeadGlyphBase> = new Map();
private _notes: Note[] = [];
private _deadSlapped: DeadSlappedBeatGlyph | null = null;
private _tremoloPicking: TremoloPickingGlyph | null = null;
Expand Down Expand Up @@ -103,62 +102,34 @@ export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase {
return this.minStepsNote ? this._internalGetNoteY(this.minStepsNote.glyph, requestedPosition) : 0;
}

private _internalGetNoteY(n: MusicFontGlyph, requestedPosition: NoteYPosition): number {
let pos = this.y + n.y;

private _internalGetNoteY(n: NoteHeadGlyphBase, requestedPosition: NoteYPosition): number {
const sr = this.renderer as ScoreBarRenderer;
const scale = this.beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
const scale = this.scale;
switch (requestedPosition) {
case NoteYPosition.TopWithStem:
// stem start
pos -=
(sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol)!.bottomY : 0) * scale;

// stem size according to duration
case NoteYPosition.TopWithStem: {
// stem start and size according to duration
let pos = this.y + n.getNoteHeadY(sr.smuflMetrics, NoteYPosition.StemUp);
pos -= sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale;
pos -= this._stemLengthExtension;

let topCenterY = sr.centerStaffStemY(this.direction);
topCenterY -= this._stemLengthExtension;

return Math.min(topCenterY, pos);

case NoteYPosition.Top:
pos -= n.height / 2;
break;
case NoteYPosition.Center:
break;
case NoteYPosition.Bottom:
pos += n.height / 2;
break;
case NoteYPosition.BottomWithStem:
pos -=
(this.renderer.smuflMetrics.stemDown.has(n.symbol)
? this.renderer.smuflMetrics.stemDown.get(n.symbol)!.topY
: -this.renderer.smuflMetrics.glyphHeights.get(n.symbol)! / 2) * scale;

// stem size according to duration
}
case NoteYPosition.BottomWithStem: {
// stem start and size according to duration
let pos = this.y + n.getNoteHeadY(sr.smuflMetrics, NoteYPosition.StemDown);
pos += sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale;
pos += this._stemLengthExtension;

let bottomCenterY = sr.centerStaffStemY(this.direction);
bottomCenterY += this._stemLengthExtension;

return Math.max(bottomCenterY, pos);

case NoteYPosition.StemUp:
pos -=
(sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol)!.bottomY : 0) * scale;
break;
case NoteYPosition.StemDown:
pos -=
(sr.smuflMetrics.stemDown.has(n.symbol)
? sr.smuflMetrics.stemDown.get(n.symbol)!.topY
: -sr.smuflMetrics.glyphHeights.get(n.symbol)! / 2) * scale;
break;
}
}

return pos;
return this.y + n.getNoteHeadY(sr.smuflMetrics, requestedPosition);
}

public addMainNoteGlyph(noteGlyph: NoteHeadGlyphBase, note: Note, noteLine: number): void {
Expand Down
39 changes: 6 additions & 33 deletions packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -484,9 +484,6 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph {
side: ScoreChordNoteHeadGroupSide,
leftOfStem: boolean
) {
const scale = this.scale;
const smufl = this.renderer.smuflMetrics;

for (const stepInfos of side.notes.values()) {
// NOTE: for now we do not displace "third" voices even further but they overlap
for (const info of stepInfos) {
Expand All @@ -499,17 +496,9 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph {
}
// shift left/right according to stem position or glyph size
else if (leftOfStem) {
// stem-up is the offset on the right side of the notehead
if (smufl.stemUp.has(info.glyph.symbol)) {
info.glyph.x -= smufl.stemUp.get(info.glyph.symbol)!.x * scale;
} else {
info.glyph.x -= smufl.glyphWidths.get(info.glyph.symbol)! * scale;
}
info.glyph.x -= info.glyph.getStemX(this.renderer.smuflMetrics, BeamDirection.Up);
} else {
// stem-down is the offset on the left side of the notehead
if (smufl.stemDown.has(info.glyph.symbol)) {
info.glyph.x += smufl.stemDown.get(info.glyph.symbol)!.x * scale;
}
info.glyph.x += info.glyph.getStemX(this.renderer.smuflMetrics, BeamDirection.Down);
}

// update side
Expand Down Expand Up @@ -573,26 +562,10 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph {
}

private _updateGroupStemXPosition(info: ScoreNoteGlyphInfo, noteGroup: ScoreChordNoteHeadGroup) {
const smufl = this.renderer.smuflMetrics;
const scale = this.scale;
let stemX: number;

if (noteGroup.direction === BeamDirection.Up || noteGroup.displacedNotes) {
if (smufl.stemUp.has(info.glyph.symbol)) {
const stemInfo = smufl.stemUp.get(info.glyph.symbol)!;
stemX = stemInfo.x * scale;
} else {
stemX = smufl.glyphWidths.get(info.glyph.symbol)! * scale;
}
} else {
if (smufl.stemDown.has(info.glyph.symbol)) {
const stemInfo = smufl.stemDown.get(info.glyph.symbol)!;
stemX = stemInfo.x * scale;
} else {
stemX = 0;
}
}

let stemX = info.glyph.getStemX(
this.renderer.smuflMetrics,
noteGroup.direction === BeamDirection.Up || noteGroup.displacedNotes ? BeamDirection.Up : BeamDirection.Down
);
stemX += this.noteStartX;

if (stemX > noteGroup.stemX) {
Expand Down
Loading
Loading