Skip to content
Merged
4 changes: 2 additions & 2 deletions packages/alphatab/src/EngravingSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ export class EngravingSettings {
//
// custom alphatab sizes
this.numberedBarRendererBarSize = this.staffLineThickness * 2;
this.numberedBarRendererBarSpacing = this.beamSpacing + this.numberedBarRendererBarSize;
this.numberedBarRendererBarSpacing = this.beamSpacing;
this.preNoteEffectPadding = 0.4 * this.oneStaffSpace;
this.postNoteEffectPadding = 0.2 * this.oneStaffSpace;
this.lineRangedGlyphDashGap = 0.5 * this.oneStaffSpace;
Expand Down Expand Up @@ -539,7 +539,7 @@ export class EngravingSettings {
public numberedBarRendererBarSpacing = 0;

/**
* The size of the dashed drawn in numbered notation to indicate the durations.
* The padding minimum between the duration dashes.
*/
public numberedDashGlyphPadding = 0;

Expand Down
6 changes: 4 additions & 2 deletions packages/alphatab/src/Environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { LetRingEffectInfo } from '@coderline/alphatab/rendering/effects/LetRing
import { LyricsEffectInfo } from '@coderline/alphatab/rendering/effects/LyricsEffectInfo';
import { MarkerEffectInfo } from '@coderline/alphatab/rendering/effects/MarkerEffectInfo';
import { NoteOrnamentEffectInfo } from '@coderline/alphatab/rendering/effects/NoteOrnamentEffectInfo';
import { NumberedBarKeySignatureEffectInfo } from '@coderline/alphatab/rendering/effects/NumberedBarKeySignatureEffectInfo';
import { OttaviaEffectInfo } from '@coderline/alphatab/rendering/effects/OttaviaEffectInfo';
import { PalmMuteEffectInfo } from '@coderline/alphatab/rendering/effects/PalmMuteEffectInfo';
import { PickSlideEffectInfo } from '@coderline/alphatab/rendering/effects/PickSlideEffectInfo';
Expand Down Expand Up @@ -530,7 +531,9 @@ export class Environment {

//
// Numbered
new NumberedBarRendererFactory([]),
new NumberedBarRendererFactory([
{ effect: new NumberedBarKeySignatureEffectInfo(), mode: EffectBandMode.OwnedTop, order: 1000 }
]),

//
// Tabs
Expand Down Expand Up @@ -861,5 +864,4 @@ export class Environment {
// so we need to declare this specific helper function and implement it in Kotlin ourselves.
array.sort((a, b) => b - a);
}

}
5 changes: 5 additions & 0 deletions packages/alphatab/src/NotationSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,11 @@ export enum NotationElement {
*/
EffectWhammyBarLine = 50,

/**
* The key signature for numbered notation staff.
*/
EffectNumberedNotationKeySignature = 51,

}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/alphatab/src/generated/EngravingSettingsJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export interface EngravingSettingsJson {
*/
numberedBarRendererBarSpacing?: number;
/**
* The size of the dashed drawn in numbered notation to indicate the durations.
* The padding minimum between the duration dashes.
*/
numberedDashGlyphPadding?: number;
/**
Expand Down
6 changes: 3 additions & 3 deletions packages/alphatab/src/rendering/BarRendererBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { Note } from '@coderline/alphatab/model/Note';
import { SimileMark } from '@coderline/alphatab/model/SimileMark';
import { type Voice, VoiceSubElement } from '@coderline/alphatab/model/Voice';
import { CanvasHelper, type ICanvas } from '@coderline/alphatab/platform/ICanvas';
import type { RenderingResources } from '@coderline/alphatab/RenderingResources';
import { BeatXPosition } from '@coderline/alphatab/rendering/BeatXPosition';
import { EffectBandContainer } from '@coderline/alphatab/rendering/EffectBandContainer';
import {
Expand All @@ -28,6 +27,7 @@ import type { BeamingHelper } from '@coderline/alphatab/rendering/utils/BeamingH
import { Bounds } from '@coderline/alphatab/rendering/utils/Bounds';
import { ElementStyleHelper } from '@coderline/alphatab/rendering/utils/ElementStyleHelper';
import type { MasterBarBounds } from '@coderline/alphatab/rendering/utils/MasterBarBounds';
import type { RenderingResources } from '@coderline/alphatab/RenderingResources';
import type { Settings } from '@coderline/alphatab/Settings';

/**
Expand Down Expand Up @@ -677,13 +677,13 @@ export class BarRendererBase {
return this.beatGlyphsStart + this.voiceContainer.getBeatX(beat, requestedPosition, useSharedSizes);
}

public getRatioPositionX(ticks: number): number {
public getRatioPositionX(ratio: number): number {
const firstOnNoteX = this.bar.isEmpty
? this.beatGlyphsStart
: this.getBeatX(this.bar.voices[0].beats[0], BeatXPosition.MiddleNotes);
const x = firstOnNoteX;
const w = this.postBeatGlyphsStart - firstOnNoteX;
return x + w * ticks;
return x + w * ratio;
}

public getNoteX(note: Note, requestedPosition: NoteXPosition): number {
Expand Down
109 changes: 77 additions & 32 deletions packages/alphatab/src/rendering/LineBarRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export abstract class LineBarRenderer extends BarRendererBase {

protected updateFirstLineY() {
const fullLineHeight = this.lineOffset * (this.heightLineCount - 1);
const actualLineHeight = (this.drawnLineCount - 1) * this.lineOffset;
const actualLineHeight = this.drawnLineCount === 0 ? 0 : (this.drawnLineCount - 1) * this.lineOffset;
const lineYOffset = this.smuflMetrics.staffLineThickness / 2;

this.firstLineY = (((fullLineHeight - actualLineHeight) / 2) | 0) - lineYOffset;
Expand Down Expand Up @@ -471,7 +471,7 @@ export abstract class LineBarRenderer extends BarRendererBase {
beamsElement: BeatSubElement
): void {
canvas.color = h.voice!.index === 0 ? this.resources.mainGlyphColor : this.resources.secondaryGlyphColor;
if (!h.isRestBeamHelper) {
if (this.shouldPaintBeamingHelper(h)) {
if (this.drawBeamHelperAsFlags(h)) {
this.paintFlag(cx, cy, canvas, h, flagsElement);
} else {
Expand All @@ -480,8 +480,13 @@ export abstract class LineBarRenderer extends BarRendererBase {
}
}

protected shouldPaintBeamingHelper(h: BeamingHelper) {
return !h.isRestBeamHelper;
}

protected abstract getFlagTopY(beat: Beat, direction: BeamDirection): number;
protected abstract getFlagBottomY(beat: Beat, direction: BeamDirection): number;

protected shouldPaintFlag(beat: Beat): boolean {
// no flags for bend grace beats
if (beat.graceType === GraceType.BendGrace) {
Expand Down Expand Up @@ -654,8 +659,8 @@ export abstract class LineBarRenderer extends BarRendererBase {
const direction: BeamDirection = this.getBeamDirection(h);
const isGrace: boolean = h.graceType !== GraceType.None;
const scaleMod: number = isGrace ? EngravingSettings.GraceScale : 1;
let barSpacing: number = (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
let barSize: number = this.smuflMetrics.beamThickness * scaleMod;
let barSpacing: number = (this.beamSpacing + this.beamThickness) * scaleMod;
let barSize: number = this.beamThickness * scaleMod;
if (direction === BeamDirection.Down) {
barSpacing = -barSpacing;
barSize = -barSize;
Expand Down Expand Up @@ -833,30 +838,49 @@ export abstract class LineBarRenderer extends BarRendererBase {

for (const v of this.helpers.beamHelpers) {
for (const h of v) {
if (h.isRestBeamHelper) {
// no stems or beams to consider
if (!this.shouldPaintBeamingHelper(h)) {
// no visible helper
}
// notes with stems
else if (h.beats.length === 1 && h.beats[0].duration >= Duration.Half) {
const tupletDirection = this.getTupletBeamDirection(h);
if (h.direction === BeamDirection.Up) {
let topY = this.getFlagTopY(h.beats[0], h.direction);
if (h.hasTuplet) {
if (h.hasTuplet && tupletDirection === h.direction) {
topY -= this.tupletSize + this.tupletOffset;
}
if (topY < maxNoteY) {
maxNoteY = topY;
}

if (h.hasTuplet && tupletDirection !== h.direction) {
let bottomY = this.getFlagBottomY(h.beats[0], tupletDirection);
bottomY += this.tupletSize + this.tupletOffset;

if (bottomY > minNoteY) {
minNoteY = bottomY;
}
}

// bottom handled via beat container bBox
} else {
let bottomY = this.getFlagBottomY(h.beats[0], h.direction);
if (h.hasTuplet) {
if (h.hasTuplet && tupletDirection === h.direction) {
bottomY += this.tupletSize + this.tupletOffset;
}
if (bottomY > minNoteY) {
minNoteY = bottomY;
}

if (h.hasTuplet && tupletDirection !== h.direction) {
let topY = this.getFlagTopY(h.beats[0], tupletDirection);
topY -= this.tupletSize + this.tupletOffset;

if (topY < maxNoteY) {
maxNoteY = topY;
}
}

// top handled via beat container bBox
}
}
Expand All @@ -865,35 +889,43 @@ export abstract class LineBarRenderer extends BarRendererBase {
else {
this.ensureBeamDrawingInfo(h, h.direction);
const drawingInfo = h.drawingInfos.get(h.direction)!;
const tupletDirection = this.getTupletBeamDirection(h);

if (h.direction === BeamDirection.Up) {
let topY = Math.min(drawingInfo.startY, drawingInfo.endY);
if (h.hasTuplet) {
if (h.hasTuplet && tupletDirection === h.direction) {
topY -= this.tupletSize + this.tupletOffset;
}

if (topY < maxNoteY) {
maxNoteY = topY;
}

const bottomY: number =
let bottomY: number =
this.getBarLineStart(h.beatOfLowestNote, h.direction) + noteOverflowPadding;
if (h.hasTuplet && tupletDirection !== h.direction) {
bottomY += this.tupletSize + this.tupletOffset;
}

if (bottomY > minNoteY) {
minNoteY = bottomY;
}
} else {
let bottomY = Math.max(drawingInfo.startY, drawingInfo.endY);

if (h.hasTuplet) {
if (h.hasTuplet && tupletDirection === h.direction) {
bottomY += this.tupletSize + this.tupletOffset;
}

if (bottomY > minNoteY) {
minNoteY = bottomY;
}

const topY: number =
this.getBarLineStart(h.beatOfHighestNote, h.direction) - noteOverflowPadding;
let topY: number = this.getBarLineStart(h.beatOfHighestNote, h.direction) - noteOverflowPadding;
if (h.hasTuplet && tupletDirection !== h.direction) {
topY -= this.tupletSize + this.tupletOffset;
}

if (topY < maxNoteY) {
maxNoteY = topY;
}
Expand All @@ -911,27 +943,12 @@ export abstract class LineBarRenderer extends BarRendererBase {
}
}

protected ensureBeamDrawingInfo(h: BeamingHelper, direction: BeamDirection): void {
if (h.drawingInfos.has(direction)) {
return;
}
const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
const barCount: number = ModelUtils.getIndex(h.shortestDuration) - 2;

protected initializeBeamDrawingInfo(h: BeamingHelper, direction: BeamDirection) {
const drawingInfo = new BeamingHelperDrawInfo();
h.drawingInfos.set(direction, drawingInfo);

// the beaming logic works like this:
// 1. we take the first and last note, add the stem, and put a diagnal line between them.
// 2. the height of the diagonal line must not exceed a max height,
// - if this is the case, the line on the more distant note just gets longer
// 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps

const firstBeat = h.beats[0];
const lastBeat = h.beats[h.beats.length - 1];

const isRest = h.isRestBeamHelper;

// 1. put direct diagonal line.
drawingInfo.startBeat = firstBeat;
drawingInfo.startX = this.getBeatX(firstBeat, BeatXPosition.Stem);
Expand Down Expand Up @@ -980,13 +997,41 @@ export abstract class LineBarRenderer extends BarRendererBase {
drawingInfo.startY = drawingInfo.endY + maxSlope;
}

return drawingInfo;
}

protected get beamSpacing() {
return this.smuflMetrics.beamSpacing;
}
protected get beamThickness() {
return this.smuflMetrics.beamThickness;
}

protected ensureBeamDrawingInfo(h: BeamingHelper, direction: BeamDirection): void {
if (h.drawingInfos.has(direction)) {
return;
}

// the beaming logic works like this:
// 1. we take the first and last note, add the stem, and put a diagnal line between them.
// 2. the height of the diagonal line must not exceed a max height,
// - if this is the case, the line on the more distant note just gets longer
// 3. any middle elements (notes or rests) shift this diagonal line up/down to avoid overlaps

const drawingInfo = this.initializeBeamDrawingInfo(h, direction);
h.drawingInfos.set(direction, drawingInfo);

const isRest = h.isRestBeamHelper;
const scale = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
const barCount: number = ModelUtils.getIndex(h.shortestDuration) - 2;

// 3. adjust beam drawing order
// we can only draw up to 2 beams towards the noteheads, then we have to grow to the other side
// here we shift accordingly
let barDrawingShift = 0;
if (barCount > 2 && !isRest) {
const beamSpacing = this.smuflMetrics.beamSpacing * scale;
const beamThickness = this.smuflMetrics.beamThickness * scale;
const beamSpacing = this.beamSpacing * scale;
const beamThickness = this.beamThickness * scale;
const totalBarsHeight = barCount * beamThickness + (barCount - 1) * beamSpacing;

if (direction === BeamDirection.Up) {
Expand Down Expand Up @@ -1038,7 +1083,7 @@ export abstract class LineBarRenderer extends BarRendererBase {
if (h.restBeats.length > 0) {
// space needed for the bars, rests need to be below them
const scaleMod: number = h.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1;
barSpacing = barCount * (this.smuflMetrics.beamSpacing + this.smuflMetrics.beamThickness) * scaleMod;
barSpacing = barCount * (this.beamSpacing + this.beamThickness) * scaleMod;
}

for (const b of h.restBeats) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export class MultiBarRestBeatContainerGlyph extends BeatContainerGlyphBase {
public override get absoluteDisplayStart(): number {
return this.renderer.bar.masterBar.start;
}
public override get beatId(): number {
return -1;
}

public override get onTimeX(): number {
return 0;
}
Expand Down Expand Up @@ -99,7 +103,6 @@ export class MultiBarRestBeatContainerGlyph extends BeatContainerGlyphBase {
case BeatXPosition.OnNotes:
case BeatXPosition.MiddleNotes:
case BeatXPosition.Stem:
return g.x + g.width / 2;
case BeatXPosition.PostNotes:
return g.x + g.width;
case BeatXPosition.EndBeat:
Expand Down
Loading