diff --git a/packages/alphatab/scripts/JsonDeclarationEmitter.ts b/packages/alphatab/scripts/JsonDeclarationEmitter.ts index ec1fbdd36..0d0bfff94 100644 --- a/packages/alphatab/scripts/JsonDeclarationEmitter.ts +++ b/packages/alphatab/scripts/JsonDeclarationEmitter.ts @@ -129,7 +129,7 @@ function cloneJsDoc(node: T, source: ts.Node, additionalTags: function createJsonMember( program: ts.Program, - input: ts.PropertyDeclaration, + input: ts.PropertyDeclaration | ts.GetAccessorDeclaration, importer: (name: string, module: string) => void ): ts.TypeElement { const typeInfo = getTypeWithNullableInfo(program, input.type ?? program.getTypeChecker().getTypeAtLocation(input.name), true, false, undefined); @@ -150,14 +150,18 @@ function createJsonMembers( input: ts.ClassDeclaration, importer: (name: string, module: string) => void ): ts.TypeElement[] { + const hasMatchingSetter = (name: string): boolean => + input.members.some(m => ts.isSetAccessorDeclaration(m) && (m.name as ts.Identifier).text === name); + return input.members .filter( m => - ts.isPropertyDeclaration(m) && - m.modifiers && - !m.modifiers.find(m => m.kind === ts.SyntaxKind.StaticKeyword) + (ts.isPropertyDeclaration(m) || ts.isGetAccessorDeclaration(m)) && + !m.modifiers?.find(m => m.kind === ts.SyntaxKind.StaticKeyword) && + !ts.getJSDocTags(m).find(t => t.tagName.text === 'json_ignore') && + (!ts.isGetAccessorDeclaration(m) || hasMatchingSetter((m.name as ts.Identifier).text)) ) - .map(m => createJsonMember(program, m as ts.PropertyDeclaration, importer)); + .map(m => createJsonMember(program, m as ts.PropertyDeclaration | ts.GetAccessorDeclaration, importer)); } let allJsonTypes: Map = new Map(); diff --git a/packages/alphatab/scripts/TypeSchema.ts b/packages/alphatab/scripts/TypeSchema.ts index dce37d296..fc23768a2 100644 --- a/packages/alphatab/scripts/TypeSchema.ts +++ b/packages/alphatab/scripts/TypeSchema.ts @@ -36,14 +36,27 @@ export function buildTypeSchema(program: ts.Program, input: ts.ClassDeclaration) hasSetPropertyExtension: false }; + const accessorHasSetter = (cls: ts.ClassDeclaration, name: string): boolean => + cls.members.some(m => ts.isSetAccessorDeclaration(m) && (m.name as ts.Identifier).text === name); + const handleMember = ( + cls: ts.ClassDeclaration, member: ts.ClassDeclaration['members'][0], typeArgumentMapping: Map | undefined ) => { - if (ts.isPropertyDeclaration(member)) { - const propertyDeclaration = member as ts.PropertyDeclaration; + if (ts.isPropertyDeclaration(member) || ts.isGetAccessorDeclaration(member)) { + // Only the getter side of an accessor pair contributes a schema entry; the setter is + // handled implicitly via the assignment generated by the serializer. A getter without + // a matching setter is a computed read-only property and is skipped — it cannot be + // assigned to and writing it via toJson would just duplicate underlying state. + if ( + ts.isGetAccessorDeclaration(member) && + !accessorHasSetter(cls, (member.name as ts.Identifier).text) + ) { + return; + } if ( - !propertyDeclaration.modifiers!.find( + !member.modifiers?.find( m => m.kind === ts.SyntaxKind.StaticKeyword || m.kind === ts.SyntaxKind.PrivateKeyword ) ) { @@ -57,20 +70,40 @@ export function buildTypeSchema(program: ts.Program, input: ts.ClassDeclaration) if (!jsDoc.find(t => t.tagName.text === 'json_ignore')) { const asRaw = !!jsDoc.find(t => t.tagName.text === 'json_raw'); const isReadonly = !!jsDoc.find(t => t.tagName.text === 'json_read_only'); + const isAccessor = ts.isGetAccessorDeclaration(member); + const isOptional = ts.isPropertyDeclaration(member) && !!member.questionToken; + + // Heuristic: a deprecated getter+setter pair is almost always a + // backwards-compat alias for a canonical property — round-tripping it via + // toJson would duplicate state. Surface a warning so a human can decide + // whether to add @json_read_only (input-only) or @json_ignore (drop entirely). + if ( + isAccessor && + !isReadonly && + jsDoc.some(t => t.tagName.text === 'deprecated') + ) { + const sourceFile = member.getSourceFile(); + const { line } = sourceFile.getLineAndCharacterOfPosition(member.getStart()); + console.warn( + `[serializer] ${sourceFile.fileName}:${line + 1} - deprecated accessor ${(member.name as ts.Identifier).text}: consider @json_read_only (input-only legacy alias) or @json_ignore (drop from JSON entirely).` + ); + } schema.properties.push({ jsonNames: jsonNames, asRaw, partialNames: !!jsDoc.find(t => t.tagName.text === 'json_partial_names'), target: jsDoc.find(t => t.tagName.text === 'target')?.comment as string, isJsonReadOnly: isReadonly, - isReadOnly: propertyDeclaration.modifiers!.some(m => m.kind == ts.SyntaxKind.ReadonlyKeyword), + isReadOnly: isAccessor + ? false + : member.modifiers!.some(m => m.kind == ts.SyntaxKind.ReadonlyKeyword), name: (member.name as ts.Identifier).text, jsDocTags: jsDoc, type: getTypeWithNullableInfo( program, member.type ?? program.getTypeChecker().getTypeAtLocation(member.name), asRaw || isReadonly, - !!member.questionToken, + isOptional, typeArgumentMapping ) }); @@ -93,7 +126,7 @@ export function buildTypeSchema(program: ts.Program, input: ts.ClassDeclaration) const checker = program.getTypeChecker(); while (hierarchy) { for (const x of hierarchy.members) { - handleMember(x, typeArgumentMapping); + handleMember(hierarchy, x, typeArgumentMapping); } const extendsClause = hierarchy.heritageClauses?.find(c => c.token === ts.SyntaxKind.ExtendsKeyword); diff --git a/packages/alphatab/src/DisplaySettings.ts b/packages/alphatab/src/DisplaySettings.ts index 4fce2772d..9c31eee07 100644 --- a/packages/alphatab/src/DisplaySettings.ts +++ b/packages/alphatab/src/DisplaySettings.ts @@ -52,6 +52,35 @@ export class DisplaySettings { */ public stretchForce: number = 1.0; + /** + * The proportional spacing ratio between successive note durations. + * @since 1.9.0 + * @category Display + * @defaultValue `Math.SQRT2` (≈ 1.414, matches Dorico's default) + * @remarks + * Controls the *shape* of the horizontal spacing curve - how much wider a note of duration `2d` is rendered relative to a note of duration `d`. + * AlphaTab uses a power-law spacing model (the same approach used by Dorico, MuseScore and Finale): doubling the note duration multiplies its + * allocated horizontal space by `spacingRatio`. + * + * Reference values for cross-application comparison: + * + * | Application / Style | Ratio | Character | + * |----------------------------|----------------------|------------------------------------| + * | Dorico default | √2 ≈ 1.414 | Tight, efficient, orchestral | + * | MuseScore default | 1.5 | Balanced, general-purpose | + * | Finale default (Fibonacci) | φ ≈ 1.618 | Loose, traditional engraving | + * + * AlphaTab defaults to `√2` (Dorico's value). This produces tighter spacing at long + * durations than the alternatives, which matters for guitar tablature where rest bars and + * whole notes are common - looser ratios make those bars dominate system width. + * + * This setting is orthogonal to {@link stretchForce}: `spacingRatio` controls the *shape* of the spacing (proportions between durations), + * `stretchForce` controls the overall *density* (how tightly or loosely the music is packed). Both can be adjusted independently. + * + * Values are clamped to the range `[1.2, 2.0]`. A value of `1.0` would produce equal spacing for all durations and is rejected. + */ + public spacingRatio: number = Math.SQRT2; + /** * The layouting mode used to arrange the the notation. * @remarks @@ -123,19 +152,65 @@ export class DisplaySettings { */ public barCountPerPartial: number = 10; + /** + * The minimum fullness ratio at which the last system in a flow is justified to fill the + * available staff width. + * @since 1.9.0 + * @category Display + * @defaultValue `1` + * @remarks + * The "fullness" of a system is its natural unjustified width divided by the available + * staff width. The last system is stretched to full width only when its fullness is + * **greater than or equal to** this threshold; otherwise it renders at its natural width. + * + * Following industry convention (Dorico, MuseScore), a sparsely populated final system + * looks better compact than spread across the full page. The threshold lets users tune + * where that boundary sits. + * + * Common values: + * + * - `1` (default) — never justify the last system. Equivalent to the legacy + * `justifyLastSystem = false` behaviour. + * - `0` — always justify the last system, even when sparse. Equivalent to the legacy + * `justifyLastSystem = true` behaviour. + * - `0.4`–`0.7` — Dorico/MuseScore-style: justify when the last system is reasonably full, + * leave compact when only a few bars trail. + * + * The threshold is bypassed when the last system is naturally wider than the available + * staff width - in that case the system still compresses to fit, since otherwise content + * would overflow horizontally. + * + * Values outside `[0, 1]` are clamped. + */ + public lastSystemFillThreshold: number = 1; + /** * Whether to justify also the last system in page layouts. + * * @remarks - * Setting this option to `true` tells alphaTab to also justify the last system (row) like it - * already does for the systems which are full. + * @deprecated Use {@link lastSystemFillThreshold} for fine-grained control over when the + * last system is justified. This property is now a thin wrapper: + * + * - **Get** returns `true` when {@link lastSystemFillThreshold} is less than `1` (i.e. some + * degree of last-system justification is enabled), `false` otherwise. + * - **Set** to `true` writes `lastSystemFillThreshold = 0` (always justify regardless of + * fullness). Set to `false` writes `lastSystemFillThreshold = 1` (never justify). + * * | Justification Disabled | Justification Enabled | * |--------------------------------------------------------------|-------------------------------------------------------| * | ![Disabled](https://alphatab.net/img/reference/property/justify-last-system-false.png) | ![Enabled](https://alphatab.net/img/reference/property/justify-last-system-true.png) | + * * @since 1.3.0 * @category Display * @defaultValue `false` - */ - public justifyLastSystem: boolean = false; + * @json_read_only + */ + public get justifyLastSystem(): boolean { + return this.lastSystemFillThreshold < 1; + } + public set justifyLastSystem(value: boolean) { + this.lastSystemFillThreshold = value ? 0 : 1; + } /** * Allows adjusting of the used fonts and colors for rendering. diff --git a/packages/alphatab/src/RenderingResources.ts b/packages/alphatab/src/RenderingResources.ts index beb2338d0..872383276 100644 --- a/packages/alphatab/src/RenderingResources.ts +++ b/packages/alphatab/src/RenderingResources.ts @@ -87,6 +87,7 @@ export class RenderingResources { * @defaultValue `bold 12px Arial, sans-serif` * @since 0.9.6 * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreCopyright} + * @json_read_only */ public get copyrightFont(): Font { return this.elementFonts.get(NotationElement.ScoreCopyright)!; @@ -103,6 +104,7 @@ export class RenderingResources { * @defaultValue `32px Georgia, serif` * @since 0.9.6 * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreTitle} + * @json_read_only */ public get titleFont(): Font { return this.elementFonts.get(NotationElement.ScoreTitle)!; @@ -120,6 +122,7 @@ export class RenderingResources { * @defaultValue `20px Georgia, serif` * @since 0.9.6 * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreSubTitle} + * @json_read_only */ public get subTitleFont(): Font { return this.elementFonts.get(NotationElement.ScoreSubTitle)!; @@ -137,6 +140,7 @@ export class RenderingResources { * @defaultValue `15px Arial, sans-serif` * @since 0.9.6 * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreWords} + * @json_read_only */ public get wordsFont(): Font { return this.elementFonts.get(NotationElement.ScoreWords)!; @@ -154,6 +158,7 @@ export class RenderingResources { * @defaultValue `12px Georgia, serif` * @since 1.4.0 * @deprecated use {@link elementFonts} with {@link NotationElement.EffectBeatTimer} + * @json_read_only */ public get timerFont(): Font { return this.elementFonts.get(NotationElement.EffectBeatTimer)!; @@ -171,6 +176,7 @@ export class RenderingResources { * @defaultValue `14px Georgia, serif` * @since 1.4.0 * @deprecated use {@link elementFonts} with {@link NotationElement.EffectDirections} + * @json_read_only */ public get directionsFont(): Font { return this.elementFonts.get(NotationElement.EffectDirections)!; @@ -188,6 +194,7 @@ export class RenderingResources { * @defaultValue `11px Arial, sans-serif` * @since 0.9.6 * @deprecated use {@link elementFonts} with {@link NotationElement.ChordDiagramFretboardNumbers} + * @json_read_only */ public get fretboardNumberFont(): Font { return this.elementFonts.get(NotationElement.ChordDiagramFretboardNumbers)!; @@ -223,6 +230,7 @@ export class RenderingResources { * @defaultValue `bold 14px Georgia, serif` * @since 0.9.6 * @deprecated use {@link elementFonts} with {@link NotationElement.EffectMarker} + * @json_read_only */ public get markerFont(): Font { return this.elementFonts.get(NotationElement.EffectMarker)!; @@ -249,6 +257,7 @@ export class RenderingResources { * @defaultValue `11px Arial, sans-serif` * @since 0.9.6 * @deprecated use {@link elementFonts} with {@link NotationElement.BarNumber} + * @json_read_only */ public get barNumberFont(): Font { return this.elementFonts.get(NotationElement.BarNumber)!; diff --git a/packages/alphatab/src/generated/DisplaySettingsJson.ts b/packages/alphatab/src/generated/DisplaySettingsJson.ts index 20cb54ddb..0a198229b 100644 --- a/packages/alphatab/src/generated/DisplaySettingsJson.ts +++ b/packages/alphatab/src/generated/DisplaySettingsJson.ts @@ -39,6 +39,34 @@ export interface DisplaySettingsJson { * | ![Default](https://alphatab.net/img/reference/property/stretchforce-default.png) | ![0.5](https://alphatab.net/img/reference/property/stretchforce-half.png) | */ stretchForce?: number; + /** + * The proportional spacing ratio between successive note durations. + * @since 1.9.0 + * @category Display + * @defaultValue `Math.SQRT2` (≈ 1.414, matches Dorico's default) + * @remarks + * Controls the *shape* of the horizontal spacing curve - how much wider a note of duration `2d` is rendered relative to a note of duration `d`. + * AlphaTab uses a power-law spacing model (the same approach used by Dorico, MuseScore and Finale): doubling the note duration multiplies its + * allocated horizontal space by `spacingRatio`. + * + * Reference values for cross-application comparison: + * + * | Application / Style | Ratio | Character | + * |----------------------------|----------------------|------------------------------------| + * | Dorico default | √2 ≈ 1.414 | Tight, efficient, orchestral | + * | MuseScore default | 1.5 | Balanced, general-purpose | + * | Finale default (Fibonacci) | φ ≈ 1.618 | Loose, traditional engraving | + * + * AlphaTab defaults to `√2` (Dorico's value). This produces tighter spacing at long + * durations than the alternatives, which matters for guitar tablature where rest bars and + * whole notes are common - looser ratios make those bars dominate system width. + * + * This setting is orthogonal to {@link stretchForce}: `spacingRatio` controls the *shape* of the spacing (proportions between durations), + * `stretchForce` controls the overall *density* (how tightly or loosely the music is packed). Both can be adjusted independently. + * + * Values are clamped to the range `[1.2, 2.0]`. A value of `1.0` would produce equal spacing for all durations and is rejected. + */ + spacingRatio?: number; /** * The layouting mode used to arrange the the notation. * @remarks @@ -104,17 +132,57 @@ export interface DisplaySettingsJson { * setting controls how many bars are placed within such a partial. */ barCountPerPartial?: number; + /** + * The minimum fullness ratio at which the last system in a flow is justified to fill the + * available staff width. + * @since 1.9.0 + * @category Display + * @defaultValue `1` + * @remarks + * The "fullness" of a system is its natural unjustified width divided by the available + * staff width. The last system is stretched to full width only when its fullness is + * **greater than or equal to** this threshold; otherwise it renders at its natural width. + * + * Following industry convention (Dorico, MuseScore), a sparsely populated final system + * looks better compact than spread across the full page. The threshold lets users tune + * where that boundary sits. + * + * Common values: + * + * - `1` (default) — never justify the last system. Equivalent to the legacy + * `justifyLastSystem = false` behaviour. + * - `0` — always justify the last system, even when sparse. Equivalent to the legacy + * `justifyLastSystem = true` behaviour. + * - `0.4`–`0.7` — Dorico/MuseScore-style: justify when the last system is reasonably full, + * leave compact when only a few bars trail. + * + * The threshold is bypassed when the last system is naturally wider than the available + * staff width - in that case the system still compresses to fit, since otherwise content + * would overflow horizontally. + * + * Values outside `[0, 1]` are clamped. + */ + lastSystemFillThreshold?: number; /** * Whether to justify also the last system in page layouts. + * * @remarks - * Setting this option to `true` tells alphaTab to also justify the last system (row) like it - * already does for the systems which are full. + * @deprecated Use {@link lastSystemFillThreshold} for fine-grained control over when the + * last system is justified. This property is now a thin wrapper: + * + * - **Get** returns `true` when {@link lastSystemFillThreshold} is less than `1` (i.e. some + * degree of last-system justification is enabled), `false` otherwise. + * - **Set** to `true` writes `lastSystemFillThreshold = 0` (always justify regardless of + * fullness). Set to `false` writes `lastSystemFillThreshold = 1` (never justify). + * * | Justification Disabled | Justification Enabled | * |--------------------------------------------------------------|-------------------------------------------------------| * | ![Disabled](https://alphatab.net/img/reference/property/justify-last-system-false.png) | ![Enabled](https://alphatab.net/img/reference/property/justify-last-system-true.png) | + * * @since 1.3.0 * @category Display * @defaultValue `false` + * @json_read_only */ justifyLastSystem?: boolean; /** diff --git a/packages/alphatab/src/generated/DisplaySettingsSerializer.ts b/packages/alphatab/src/generated/DisplaySettingsSerializer.ts index c07b064b7..5c21a52e6 100644 --- a/packages/alphatab/src/generated/DisplaySettingsSerializer.ts +++ b/packages/alphatab/src/generated/DisplaySettingsSerializer.ts @@ -26,13 +26,14 @@ export class DisplaySettingsSerializer { const o = new Map(); o.set("scale", obj.scale); o.set("stretchforce", obj.stretchForce); + o.set("spacingratio", obj.spacingRatio); o.set("layoutmode", obj.layoutMode as number); o.set("staveprofile", obj.staveProfile as number); o.set("barsperrow", obj.barsPerRow); o.set("startbar", obj.startBar); o.set("barcount", obj.barCount); o.set("barcountperpartial", obj.barCountPerPartial); - o.set("justifylastsystem", obj.justifyLastSystem); + o.set("lastsystemfillthreshold", obj.lastSystemFillThreshold); o.set("resources", RenderingResourcesSerializer.toJson(obj.resources)); o.set("padding", obj.padding); o.set("firstsystempaddingtop", obj.firstSystemPaddingTop); @@ -64,6 +65,9 @@ export class DisplaySettingsSerializer { case "stretchforce": obj.stretchForce = v! as number; return true; + case "spacingratio": + obj.spacingRatio = v! as number; + return true; case "layoutmode": obj.layoutMode = JsonHelper.parseEnum(v, LayoutMode)!; return true; @@ -82,6 +86,9 @@ export class DisplaySettingsSerializer { case "barcountperpartial": obj.barCountPerPartial = v! as number; return true; + case "lastsystemfillthreshold": + obj.lastSystemFillThreshold = v! as number; + return true; case "justifylastsystem": obj.justifyLastSystem = v! as boolean; return true; diff --git a/packages/alphatab/src/generated/RenderingResourcesJson.ts b/packages/alphatab/src/generated/RenderingResourcesJson.ts index 52e6d6ad0..50a2d7a8d 100644 --- a/packages/alphatab/src/generated/RenderingResourcesJson.ts +++ b/packages/alphatab/src/generated/RenderingResourcesJson.ts @@ -38,29 +38,77 @@ export interface RenderingResourcesJson { */ engravingSettings?: EngravingSettingsJson; /** - * Unused, see deprecation note. - * @defaultValue `14px Georgia, serif` + * The font to use for displaying the songs copyright information in the header of the music sheet. + * @defaultValue `bold 12px Arial, sans-serif` + * @since 0.9.6 + * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreCopyright} + * @json_read_only + */ + copyrightFont?: FontJson; + /** + * The font to use for displaying the songs title in the header of the music sheet. + * @defaultValue `32px Georgia, serif` * @since 0.9.6 - * @deprecated Since 1.7.0 alphaTab uses the glyphs contained in the SMuFL font - * @json_ignore + * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreTitle} + * @json_read_only */ - fingeringFont?: FontJson; + titleFont?: FontJson; /** - * Unused, see deprecation note. + * The font to use for displaying the songs subtitle in the header of the music sheet. + * @defaultValue `20px Georgia, serif` + * @since 0.9.6 + * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreSubTitle} + * @json_read_only + */ + subTitleFont?: FontJson; + /** + * The font to use for displaying the lyrics information in the header of the music sheet. + * @defaultValue `15px Arial, sans-serif` + * @since 0.9.6 + * @deprecated use {@link elementFonts} with {@link NotationElement.ScoreWords} + * @json_read_only + */ + wordsFont?: FontJson; + /** + * The font to use for displaying beat time information in the music sheet. * @defaultValue `12px Georgia, serif` * @since 1.4.0 - * @deprecated Since 1.7.0 alphaTab uses the glyphs contained in the SMuFL font - * @json_ignore + * @deprecated use {@link elementFonts} with {@link NotationElement.EffectBeatTimer} + * @json_read_only + */ + timerFont?: FontJson; + /** + * The font to use for displaying the directions texts. + * @defaultValue `14px Georgia, serif` + * @since 1.4.0 + * @deprecated use {@link elementFonts} with {@link NotationElement.EffectDirections} + * @json_read_only */ - inlineFingeringFont?: FontJson; + directionsFont?: FontJson; /** - * Ununsed, see deprecation note. - * @defaultValue `italic 12px Georgia, serif` + * The font to use for displaying the fretboard numbers in chord diagrams. + * @defaultValue `11px Arial, sans-serif` + * @since 0.9.6 + * @deprecated use {@link elementFonts} with {@link NotationElement.ChordDiagramFretboardNumbers} + * @json_read_only + */ + fretboardNumberFont?: FontJson; + /** + * The font to use for section marker labels shown above the music sheet. + * @defaultValue `bold 14px Georgia, serif` + * @since 0.9.6 + * @deprecated use {@link elementFonts} with {@link NotationElement.EffectMarker} + * @json_read_only + */ + markerFont?: FontJson; + /** + * The font to use for displaying the bar numbers above the music sheet. + * @defaultValue `11px Arial, sans-serif` * @since 0.9.6 - * @deprecated use {@link elementFonts} with the respective - * @json_ignore + * @deprecated use {@link elementFonts} with {@link NotationElement.BarNumber} + * @json_read_only */ - effectFont?: FontJson; + barNumberFont?: FontJson; /** * The fonts used by individual elements. Check `defaultFonts` for the elements which have custom fonts. * Removing fonts from this map can lead to unexpected side effects and errors. Only update it with new values. diff --git a/packages/alphatab/src/generated/RenderingResourcesSerializer.ts b/packages/alphatab/src/generated/RenderingResourcesSerializer.ts index b43047b54..67abbc6e2 100644 --- a/packages/alphatab/src/generated/RenderingResourcesSerializer.ts +++ b/packages/alphatab/src/generated/RenderingResourcesSerializer.ts @@ -50,6 +50,33 @@ export class RenderingResourcesSerializer { case "smuflfontfamilyname": obj.smuflFontFamilyName = v as string | undefined; return true; + case "copyrightfont": + obj.copyrightFont = Font.fromJson(v)!; + return true; + case "titlefont": + obj.titleFont = Font.fromJson(v)!; + return true; + case "subtitlefont": + obj.subTitleFont = Font.fromJson(v)!; + return true; + case "wordsfont": + obj.wordsFont = Font.fromJson(v)!; + return true; + case "timerfont": + obj.timerFont = Font.fromJson(v)!; + return true; + case "directionsfont": + obj.directionsFont = Font.fromJson(v)!; + return true; + case "fretboardnumberfont": + obj.fretboardNumberFont = Font.fromJson(v)!; + return true; + case "markerfont": + obj.markerFont = Font.fromJson(v)!; + return true; + case "barnumberfont": + obj.barNumberFont = Font.fromJson(v)!; + return true; case "elementfonts": JsonHelper.forEach(v, (v, k) => { obj.elementFonts.set(JsonHelper.parseEnum(k, NotationElement)!, Font.fromJson(v)!); diff --git a/packages/alphatab/src/generated/model/BeatSerializer.ts b/packages/alphatab/src/generated/model/BeatSerializer.ts index bcc414c25..adbaeba78 100644 --- a/packages/alphatab/src/generated/model/BeatSerializer.ts +++ b/packages/alphatab/src/generated/model/BeatSerializer.ts @@ -141,6 +141,9 @@ export class BeatSerializer { case "dots": obj.dots = v! as number; return true; + case "fadein": + obj.fadeIn = v! as boolean; + return true; case "fade": obj.fade = JsonHelper.parseEnum(v, FadeType)!; return true; @@ -214,6 +217,9 @@ export class BeatSerializer { obj.tremoloPicking = undefined; } return true; + case "tremolospeed": + obj.tremoloSpeed = JsonHelper.parseEnum(v, Duration) ?? null; + return true; case "crescendo": obj.crescendo = JsonHelper.parseEnum(v, CrescendoType)!; return true; diff --git a/packages/alphatab/src/generated/model/MasterBarSerializer.ts b/packages/alphatab/src/generated/model/MasterBarSerializer.ts index 4c9d2b9d5..af898c27f 100644 --- a/packages/alphatab/src/generated/model/MasterBarSerializer.ts +++ b/packages/alphatab/src/generated/model/MasterBarSerializer.ts @@ -9,6 +9,8 @@ import { BeamingRulesSerializer } from "@coderline/alphatab/generated/model/Beam import { SectionSerializer } from "@coderline/alphatab/generated/model/SectionSerializer"; import { AutomationSerializer } from "@coderline/alphatab/generated/model/AutomationSerializer"; import { FermataSerializer } from "@coderline/alphatab/generated/model/FermataSerializer"; +import { KeySignature } from "@coderline/alphatab/model/KeySignature"; +import { KeySignatureType } from "@coderline/alphatab/model/KeySignatureType"; import { BeamingRules } from "@coderline/alphatab/model/MasterBar"; import { TripletFeel } from "@coderline/alphatab/model/TripletFeel"; import { Section } from "@coderline/alphatab/model/Section"; @@ -74,6 +76,12 @@ export class MasterBarSerializer { case "alternateendings": obj.alternateEndings = v! as number; return true; + case "keysignature": + obj.keySignature = JsonHelper.parseEnum(v, KeySignature)!; + return true; + case "keysignaturetype": + obj.keySignatureType = JsonHelper.parseEnum(v, KeySignatureType)!; + return true; case "isdoublebar": obj.isDoubleBar = v! as boolean; return true; diff --git a/packages/alphatab/src/model/Beat.ts b/packages/alphatab/src/model/Beat.ts index a0cbf8df0..c110b0190 100644 --- a/packages/alphatab/src/model/Beat.ts +++ b/packages/alphatab/src/model/Beat.ts @@ -364,6 +364,7 @@ export class Beat { /** * Gets a value indicating whether this beat is fade-in. * @deprecated Use `fade` + * @json_read_only */ public get fadeIn(): boolean { return this.fade === FadeType.FadeIn; @@ -551,6 +552,7 @@ export class Beat { /** * The speed of the tremolo. * @deprecated Set {@link tremoloPicking} instead. + * @json_read_only */ public get tremoloSpeed(): Duration | null { const tremolo = this.tremoloPicking; diff --git a/packages/alphatab/src/model/MasterBar.ts b/packages/alphatab/src/model/MasterBar.ts index ed1982268..bd4473b12 100644 --- a/packages/alphatab/src/model/MasterBar.ts +++ b/packages/alphatab/src/model/MasterBar.ts @@ -211,6 +211,7 @@ export class MasterBar { /** * The key signature used on all bars. * @deprecated Use key signatures on bar level + * @json_read_only */ public get keySignature(): KeySignature { return this.score.tracks[0].staves[0].bars[this.index].keySignature; @@ -227,6 +228,7 @@ export class MasterBar { /** * The type of key signature (major/minor) * @deprecated Use key signatures on bar level + * @json_read_only */ public get keySignatureType(): KeySignatureType { return this.score.tracks[0].staves[0].bars[this.index].keySignatureType; diff --git a/packages/alphatab/src/rendering/BarRendererBase.ts b/packages/alphatab/src/rendering/BarRendererBase.ts index dd015bec6..efd7e0438 100644 --- a/packages/alphatab/src/rendering/BarRendererBase.ts +++ b/packages/alphatab/src/rendering/BarRendererBase.ts @@ -4,7 +4,7 @@ import { MusicFontSymbol } from '@coderline/alphatab/model/MusicFontSymbol'; 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 { CanvasHelper, type ICanvas, TextAlign } from '@coderline/alphatab/platform/ICanvas'; import { BeatXPosition } from '@coderline/alphatab/rendering/BeatXPosition'; import { EffectBandContainer } from '@coderline/alphatab/rendering/EffectBandContainer'; import { @@ -13,7 +13,9 @@ import { } from '@coderline/alphatab/rendering/glyphs/BeatContainerGlyph'; import type { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; import { LeftToRightLayoutingGlyphGroup } from '@coderline/alphatab/rendering/glyphs/LeftToRightLayoutingGlyphGroup'; +import type { LyricsGlyph } from '@coderline/alphatab/rendering/glyphs/LyricsGlyph'; import { MultiVoiceContainerGlyph } from '@coderline/alphatab/rendering/glyphs/MultiVoiceContainerGlyph'; +import type { TextGlyph } from '@coderline/alphatab/rendering/glyphs/TextGlyph'; import { ContinuationTieGlyph, type ITieGlyph, type TieGlyph } from '@coderline/alphatab/rendering/glyphs/TieGlyph'; import { MultiBarRestBeatContainerGlyph } from '@coderline/alphatab/rendering/MultiBarRestBeatContainerGlyph'; import type { ScoreRenderer } from '@coderline/alphatab/rendering/ScoreRenderer'; @@ -283,6 +285,38 @@ export class BarRendererBase { } } + public _registerOverlayRods(): void { + const info: BarLayoutingInfo = this.layoutingInfo; + this._collectOverlayRods(this.topEffects, info); + this._collectOverlayRods(this.bottomEffects, info); + } + + private _collectOverlayRods(container: EffectBandContainer, info: BarLayoutingInfo): void { + for (const band of container.bands) { + if (!band.info.contributesOverlayRods) { + continue; + } + const bandKey = String(band.info.notationElement); + for (const glyph of band.iterateAllGlyphs()) { + if (!glyph.beat || glyph.width <= 0) { + continue; + } + const w = glyph.width; + const align = (glyph as LyricsGlyph | TextGlyph).textAlign; + let leftExtent = w * 0.5; + let rightExtent = w * 0.5; + if (align === TextAlign.Left) { + leftExtent = 0; + rightExtent = w; + } else if (align === TextAlign.Right) { + leftExtent = w; + rightExtent = 0; + } + info.addOverlayRod(bandKey, glyph.beat.absoluteDisplayStart, leftExtent, rightExtent); + } + } + } + private _appliedLayoutingInfo: number = 0; public afterReverted() { @@ -436,6 +470,7 @@ export class BarRendererBase { this.createPostBeatGlyphs(); this._registerLayoutingInfo(); + this._registerOverlayRods(); // registering happened during creation this.topEffects.sizeAndAlignEffectBands(false); @@ -691,6 +726,7 @@ export class BarRendererBase { } this._registerLayoutingInfo(); + this._registerOverlayRods(); this.calculateOverflows(0, this.height); } diff --git a/packages/alphatab/src/rendering/EffectBandContainer.ts b/packages/alphatab/src/rendering/EffectBandContainer.ts index b0271b22f..f5cd97e21 100644 --- a/packages/alphatab/src/rendering/EffectBandContainer.ts +++ b/packages/alphatab/src/rendering/EffectBandContainer.ts @@ -28,6 +28,10 @@ export class EffectBandContainer { } } + public get bands(): EffectBand[] { + return this._bands; + } + public get previousContainer(): EffectBandContainer | undefined { return this._renderer.index === 0 ? undefined diff --git a/packages/alphatab/src/rendering/EffectInfo.ts b/packages/alphatab/src/rendering/EffectInfo.ts index 94a830077..0ce5d9129 100644 --- a/packages/alphatab/src/rendering/EffectInfo.ts +++ b/packages/alphatab/src/rendering/EffectInfo.ts @@ -55,6 +55,15 @@ export abstract class EffectInfo { */ public abstract get sizingMode(): EffectBarGlyphSizing; + /** + * Gets a value indicating whether glyphs created by this effect contribute + * overlay rods used during bar spacing. Defaults to false; override and + * return true to opt in. + */ + public get contributesOverlayRods(): boolean { + return false; + } + /** * Creates a new effect glyph for the given beat. * @param renderer the renderer which requests for glyph creation diff --git a/packages/alphatab/src/rendering/effects/FreeTimeEffectInfo.ts b/packages/alphatab/src/rendering/effects/FreeTimeEffectInfo.ts index 848b69c03..862d791b5 100644 --- a/packages/alphatab/src/rendering/effects/FreeTimeEffectInfo.ts +++ b/packages/alphatab/src/rendering/effects/FreeTimeEffectInfo.ts @@ -24,6 +24,10 @@ export class FreeTimeEffectInfo extends EffectInfo { return true; } + public override get contributesOverlayRods(): boolean { + return true; + } + public get sizingMode(): EffectBarGlyphSizing { return EffectBarGlyphSizing.SinglePreBeat; } diff --git a/packages/alphatab/src/rendering/effects/LyricsEffectInfo.ts b/packages/alphatab/src/rendering/effects/LyricsEffectInfo.ts index 30972c7ce..4f1109f7b 100644 --- a/packages/alphatab/src/rendering/effects/LyricsEffectInfo.ts +++ b/packages/alphatab/src/rendering/effects/LyricsEffectInfo.ts @@ -28,6 +28,10 @@ export class LyricsEffectInfo extends EffectInfo { return EffectBarGlyphSizing.SingleOnBeat; } + public override get contributesOverlayRods(): boolean { + return true; + } + public shouldCreateGlyph(_settings: Settings, beat: Beat): boolean { return !!beat.lyrics; } diff --git a/packages/alphatab/src/rendering/effects/TextEffectInfo.ts b/packages/alphatab/src/rendering/effects/TextEffectInfo.ts index e9efe13f9..b5b894fbe 100644 --- a/packages/alphatab/src/rendering/effects/TextEffectInfo.ts +++ b/packages/alphatab/src/rendering/effects/TextEffectInfo.ts @@ -28,6 +28,10 @@ export class TextEffectInfo extends EffectInfo { return EffectBarGlyphSizing.SingleOnBeat; } + public override get contributesOverlayRods(): boolean { + return true; + } + public shouldCreateGlyph(_settings: Settings, beat: Beat): boolean { return !!beat.text; } diff --git a/packages/alphatab/src/rendering/glyphs/AccidentalGroupGlyph.ts b/packages/alphatab/src/rendering/glyphs/AccidentalGroupGlyph.ts index 91734169f..fd6b24828 100644 --- a/packages/alphatab/src/rendering/glyphs/AccidentalGroupGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/AccidentalGroupGlyph.ts @@ -73,6 +73,8 @@ export class AccidentalGroupGlyph extends GlyphGroup { column.x = this.width; } + this.width += this.renderer.smuflMetrics.preBeatGlyphSpacing; + for (let i: number = 0, j: number = this.glyphs.length; i < j; i++) { const g: Glyph = this.glyphs[i]; diff --git a/packages/alphatab/src/rendering/glyphs/LyricsGlyph.ts b/packages/alphatab/src/rendering/glyphs/LyricsGlyph.ts index 61841b4c2..59f94d73d 100644 --- a/packages/alphatab/src/rendering/glyphs/LyricsGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/LyricsGlyph.ts @@ -31,6 +31,7 @@ export class LyricsGlyph extends EffectGlyph { this._linePositions.push(y); const size = canvas.measureText(line.length > 0 ? line : ' '); y += size.height + lineSpacing; + this.width = Math.max(this.width, size.width); } y -= lineSpacing; diff --git a/packages/alphatab/src/rendering/layout/VerticalLayoutBase.ts b/packages/alphatab/src/rendering/layout/VerticalLayoutBase.ts index 93ee2543b..c6e7ac1ab 100644 --- a/packages/alphatab/src/rendering/layout/VerticalLayoutBase.ts +++ b/packages/alphatab/src/rendering/layout/VerticalLayoutBase.ts @@ -403,11 +403,29 @@ export abstract class VerticalLayoutBase extends ScoreLayout { // Reconcile now - it's a no-op when nothing changed. system.reconcileMinDurationIfDirty(); - if (system.isFull || system.width > this._maxWidth || this.renderer.settings.display.justifyLastSystem) { - this._scaleToWidth(system, this._maxWidth); - } else { - this._scaleToWidth(system, system.width); + const display = this.renderer.settings.display; + const overflowsAvailable = system.width > this._maxWidth; + let shouldJustify = system.isFull || overflowsAvailable; + + // Last-system fill threshold (industry convention from Dorico / MuseScore): the last + // system in a flow is justified to fill the row only when its natural fullness meets + // the configured `lastSystemFillThreshold`. A threshold of 0 always justifies (matches + // legacy `justifyLastSystem = true`); a threshold of 1 never justifies (matches legacy + // `justifyLastSystem = false`); intermediate values yield Dorico-style "justify only + // when reasonably full" behaviour. + // + // Overflow is handled before the threshold: if the natural width already exceeds the + // available staff width, the system must compress to fit regardless of the threshold, + // since otherwise content would overflow horizontally. + if (system.isLast && !shouldJustify && this._maxWidth > 0) { + const threshold = Math.max(0, Math.min(1, display.lastSystemFillThreshold)); + const fillRatio = system.width / this._maxWidth; + if (fillRatio >= threshold) { + shouldJustify = true; + } } + + this._scaleToWidth(system, shouldJustify ? this._maxWidth : system.width); system.finalizeSystem(); } diff --git a/packages/alphatab/src/rendering/staves/BarLayoutingInfo.ts b/packages/alphatab/src/rendering/staves/BarLayoutingInfo.ts index 3333f03ff..1cd32f302 100644 --- a/packages/alphatab/src/rendering/staves/BarLayoutingInfo.ts +++ b/packages/alphatab/src/rendering/staves/BarLayoutingInfo.ts @@ -16,6 +16,19 @@ interface BarLayoutingInfoBeatSizes { onBeatSize: number; } +/** + * Minimum-distance constraint contributed by overlay content (lyrics, beat text) + * attached to a beat. Records how far the overlay extends left/right of the beat's + * onTime anchor. + * @internal + * @record + */ +interface OverlayRod { + timePosition: number; + leftExtent: number; + rightExtent: number; +} + /** * This public class stores size information about a stave. * It is used by the layout engine to collect the sizes of score parts @@ -24,7 +37,27 @@ interface BarLayoutingInfoBeatSizes { */ export class BarLayoutingInfo { private static readonly _defaultMinDuration: number = 30; - private static readonly _defaultMinDurationWidth: number = 7; + private static readonly _defaultMinDurationWidth: number = 6.5; + + // Valid range for `DisplaySettings.spacingRatio`. Outside this band the layout + // degenerates (collapse below 1.2, runaway above 2.0). + private static readonly _spacingRatioMin: number = 1.2; + private static readonly _spacingRatioMax: number = 2.0; + + /** + * Power-law exponent for the spring formula, from `DisplaySettings.spacingRatio`. + * Clamps to `[_spacingRatioMin, _spacingRatioMax]`. By construction + * `phi(2*dmin, dmin) === spacingRatio`. + */ + public static spacingExponentFromRatio(spacingRatio: number): number { + let r = spacingRatio; + if (r < BarLayoutingInfo._spacingRatioMin) { + r = BarLayoutingInfo._spacingRatioMin; + } else if (r > BarLayoutingInfo._spacingRatioMax) { + r = BarLayoutingInfo._spacingRatioMax; + } + return Math.log2(r); + } private _timeSortedSprings: Spring[] = []; private _minTime: number = -1; @@ -33,9 +66,35 @@ export class BarLayoutingInfo { private _incompleteGraceRodsWidth: number = 0; private _beatSizes: Map = new Map(); + /** + * Overlay rods bucketed per visual band. Outer key is a `bandKey` (typically the + * band's `NotationElement` stringified). Inner map keyed by `Spring.timePosition`. + * Rods in different bands occupy different vertical tracks and never collide; + * pair-overlap evaluates each band independently. Same-band, same-timePosition + * registrations (lyric-on-score + lyric-on-tab for one beat) max-merge. + */ + private _overlayRodsByBand: Map> = new Map(); + + /** + * Per-band time-sorted view of {@link _overlayRodsByBand}. Maintained on insert + * via the same insertion-sort {@link addSpring} uses on {@link _timeSortedSprings}. + */ + private _timeSortedOverlayRodsByBand: Map = new Map(); + // the smallest duration we have between two springs to ensure we have positive spring constants private _minDuration: number = BarLayoutingInfo._defaultMinDuration; + /** Precomputed `log2(spacingRatio)`. Default matches `DisplaySettings.spacingRatio = √2`. */ + private readonly _spacingExponent: number; + + // Safety floor preventing overlay items from touching at the minimum-force boundary + // (rare; in normal layouts justification slack dominates). + private static readonly _overlayMinPadding: number = 3; + + public constructor(spacingRatio: number = Math.SQRT2) { + this._spacingExponent = BarLayoutingInfo.spacingExponentFromRatio(spacingRatio); + } + /** * an internal version number that increments whenever a change was made. */ @@ -77,7 +136,7 @@ export class BarLayoutingInfo { } return undefined; } - + public setBeatSizes(beat: BeatContainerGlyphBase, sizes: BarLayoutingInfoBeatSizes) { const key = beat.absoluteDisplayStart; if (this._beatSizes.has(key)) { @@ -237,6 +296,41 @@ export class BarLayoutingInfo { } } + /** + * Registers an overlay rod for a beat into the bucket identified by `bandKey` + * (typically `String(band.info.notationElement)`). Same-band, same-timePosition + * duplicates max-merge. + */ + public addOverlayRod(bandKey: string, timePosition: number, leftExtent: number, rightExtent: number): void { + this.version++; + let bandMap = this._overlayRodsByBand.get(bandKey); + let bandSorted = this._timeSortedOverlayRodsByBand.get(bandKey); + if (!bandMap) { + bandMap = new Map(); + this._overlayRodsByBand.set(bandKey, bandMap); + bandSorted = []; + this._timeSortedOverlayRodsByBand.set(bandKey, bandSorted); + } + const rod = bandMap.get(timePosition); + if (!rod) { + const newRod: OverlayRod = { timePosition, leftExtent, rightExtent }; + bandMap.set(timePosition, newRod); + const timeSorted: OverlayRod[] = bandSorted!; + let insertPos: number = timeSorted.length - 1; + while (insertPos > 0 && timeSorted[insertPos].timePosition > timePosition) { + insertPos--; + } + timeSorted.splice(insertPos + 1, 0, newRod); + } else { + if (rod.leftExtent < leftExtent) { + rod.leftExtent = leftExtent; + } + if (rod.rightExtent < rightExtent) { + rod.rightExtent = rightExtent; + } + } + } + public finish(): void { for (const [_, s] of this.allGraceRods) { // for grace beats we store the offset @@ -301,10 +395,6 @@ export class BarLayoutingInfo { // calculate the force required to have at least the minimum size. this.minStretchForce = 0; - // We take the space required between current and next spring - // and calculate the force needed so that the current spring - // reserves enough space - for (let i: number = 0; i < sortedSprings.length; i++) { const currentSpring = sortedSprings[i]; let requiredSpace = 0; @@ -325,6 +415,79 @@ export class BarLayoutingInfo { const requiredSpaceForce = requiredSpace * currentSpring.springConstant; this._updateMinStretchForce(requiredSpaceForce); } + + // Overlay rods: pair-overlap + last-rod phantom-next-beat per band. Bands + // occupy different vertical tracks (lyric below, beat-text above, ...) so + // each bucket is evaluated independently and their forces max-merge into + // `minStretchForce`. + // TODO(overlay-rods, cross-bar): bar-local only. A system-level accumulator + // could pair bar N's last rod with bar N+1's first rod across the boundary. + const overlayPadding = BarLayoutingInfo._overlayMinPadding; + for (const rods of this._timeSortedOverlayRodsByBand.values()) { + this._applyOverlayRodConstraints(rods, sortedSprings, overlayPadding); + } + } + + /** + * Pair-overlap + last-rod phantom-next-beat for a single band's rod list. + * Called once per band by {@link _calculateSpringConstants}. + */ + private _applyOverlayRodConstraints( + rods: OverlayRod[], + sortedSprings: Spring[], + overlayPadding: number + ): void { + if (rods.length === 0) { + return; + } + + // Pair-overlap pass: for each adjacent (A, B), sum 1/k over the springs + // anchored in [A.timePosition, B.timePosition) and convert the required gap + // `A.rightExtent + B.leftExtent + padding` to a force `requiredGap / invSum`. + let springIdx = 0; + while ( + springIdx < sortedSprings.length && + sortedSprings[springIdx].timePosition !== rods[0].timePosition + ) { + springIdx++; + } + + for (let r = 1; r < rods.length; r++) { + const a = rods[r - 1]; + const b = rods[r]; + + let invSum = 0; + while ( + springIdx < sortedSprings.length && + sortedSprings[springIdx].timePosition !== b.timePosition + ) { + invSum += 1 / sortedSprings[springIdx].springConstant; + springIdx++; + } + + const requiredGap = a.rightExtent + b.leftExtent + overlayPadding; + if (requiredGap > 0 && invSum > 0) { + const overlayForce = requiredGap / invSum; + this._updateMinStretchForce(overlayForce); + } + } + + // Last-rod phantom-next-beat: treat the bar's right edge as a phantom beat + // with leftExtent=0. Natural gap = force/k_last + postBeatSize, floored by + // lastSpring.postSpringWidth + postBeatSize. Fires only on overflow. + // TODO: symmetric handling for the first beat's + // LEFT edge is an accepted MVP gap (no force-scaled gap before first onTime). + const lastRod = rods[rods.length - 1]; + const lastSpring = sortedSprings[sortedSprings.length - 1]; + if (lastRod.timePosition === lastSpring.timePosition) { + const overlayRightRequirement = lastRod.rightExtent + overlayPadding; + const naturalRightBudget = lastSpring.postSpringWidth + this.postBeatSize; + if (overlayRightRequirement > naturalRightBudget) { + const requiredForce = + (overlayRightRequirement - this.postBeatSize) * lastSpring.springConstant; + this._updateMinStretchForce(requiredForce); + } + } } public height: number = 0; @@ -380,7 +543,12 @@ export class BarLayoutingInfo { const minDurationWidth = BarLayoutingInfo._defaultMinDurationWidth; - const phi: number = 1 + 0.85 * Math.log2(duration / minDuration); + // Power-law (Dorico/MuseScore/Finale) model: phi grows as a configurable power of the + // duration ratio so that doubling the duration multiplies horizontal allocation by + // exactly `spacingRatio` (= 2 ^ _spacingExponent). Replaces the previous additive + // `1 + 0.85 * log2(d/dmin)` formula which produced a compressing ratio at long + // durations and caused rest-only bars to balloon under high stretch force. + const phi: number = Math.pow(duration / minDuration, this._spacingExponent); return (smallestDuration / duration) * (1 / (phi * minDurationWidth)); } diff --git a/packages/alphatab/src/rendering/staves/StaffSystem.ts b/packages/alphatab/src/rendering/staves/StaffSystem.ts index 1fa922a1f..1d4c1cbfd 100644 --- a/packages/alphatab/src/rendering/staves/StaffSystem.ts +++ b/packages/alphatab/src/rendering/staves/StaffSystem.ts @@ -337,7 +337,7 @@ export class StaffSystem { ): MasterBarsRenderers { const result: MasterBarsRenderers = new MasterBarsRenderers(); result.additionalMultiBarRestIndexes = additionalMultiBarRestIndexes; - result.layoutingInfo = new BarLayoutingInfo(); + result.layoutingInfo = new BarLayoutingInfo(this.layout.renderer.settings.display.spacingRatio); result.masterBar = tracks[0].score.masterBars[barIndex]; this.masterBarsRenderers.push(result); diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-a.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-a.png index 73cab5c2a..04315f3f6 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-a.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-a.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-ab.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-ab.png index 9d6aa2fcf..e0fb54b6d 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-ab.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-ab.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-b.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-b.png index b6102fd8d..491f40f22 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-b.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-b.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-bb.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-bb.png index 86d63f1ae..44e1d5ae1 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-bb.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-bb.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-c.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-c.png index ce395d829..c2b5af4d4 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-c.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-c.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-cb.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-cb.png index 3249afb2a..4a9403cc6 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-cb.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-cb.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-csharp.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-csharp.png index a8102f4b8..33ee026e6 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-csharp.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-csharp.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-d.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-d.png index de9a3e19e..827af6e17 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-d.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-d.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-db.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-db.png index 86b58fc52..9107cf608 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-db.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-db.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-e.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-e.png index 4b4399d2d..03d43353d 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-e.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-e.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-eb.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-eb.png index e2416bba5..4bcac72d9 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-eb.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-eb.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-f.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-f.png index cd1ba9aed..945aaad80 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-f.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-f.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-fsharp.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-fsharp.png index 284e8b738..adf87fee9 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-fsharp.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-fsharp.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-g.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-g.png index 84790605a..72446256a 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-g.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-g.png differ diff --git a/packages/alphatab/test-data/guitarpro8/transposition-tonality-gb.png b/packages/alphatab/test-data/guitarpro8/transposition-tonality-gb.png index e6525fee3..efc636124 100644 Binary files a/packages/alphatab/test-data/guitarpro8/transposition-tonality-gb.png and b/packages/alphatab/test-data/guitarpro8/transposition-tonality-gb.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/BeetAnGeSample.png b/packages/alphatab/test-data/musicxml-samples/BeetAnGeSample.png index 11680dd98..e18daaeff 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/BeetAnGeSample.png and b/packages/alphatab/test-data/musicxml-samples/BeetAnGeSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/Binchois.png b/packages/alphatab/test-data/musicxml-samples/Binchois.png index 98c59937a..cb3a39adf 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/Binchois.png and b/packages/alphatab/test-data/musicxml-samples/Binchois.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/BrahWiMeSample.png b/packages/alphatab/test-data/musicxml-samples/BrahWiMeSample.png index a4258a4db..a8eace236 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/BrahWiMeSample.png and b/packages/alphatab/test-data/musicxml-samples/BrahWiMeSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/BrookeWestSample.png b/packages/alphatab/test-data/musicxml-samples/BrookeWestSample.png index e8a309eeb..42ca6116a 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/BrookeWestSample.png and b/packages/alphatab/test-data/musicxml-samples/BrookeWestSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/Chant.png b/packages/alphatab/test-data/musicxml-samples/Chant.png index f87c5a216..e0f04f5e5 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/Chant.png and b/packages/alphatab/test-data/musicxml-samples/Chant.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/DebuMandSample.png b/packages/alphatab/test-data/musicxml-samples/DebuMandSample.png index 743b5ba81..82c6e7221 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/DebuMandSample.png and b/packages/alphatab/test-data/musicxml-samples/DebuMandSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/Dichterliebe01.png b/packages/alphatab/test-data/musicxml-samples/Dichterliebe01.png index 1d8480bd6..efc32115e 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/Dichterliebe01.png and b/packages/alphatab/test-data/musicxml-samples/Dichterliebe01.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/Echigo.png b/packages/alphatab/test-data/musicxml-samples/Echigo.png index ebefe64d5..2e74d30cd 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/Echigo.png and b/packages/alphatab/test-data/musicxml-samples/Echigo.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/FaurReveSample.png b/packages/alphatab/test-data/musicxml-samples/FaurReveSample.png index 887ced6d4..60aa6fc1c 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/FaurReveSample.png and b/packages/alphatab/test-data/musicxml-samples/FaurReveSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/MahlFaGe4Sample.png b/packages/alphatab/test-data/musicxml-samples/MahlFaGe4Sample.png index 764df91de..9398f3cf0 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/MahlFaGe4Sample.png and b/packages/alphatab/test-data/musicxml-samples/MahlFaGe4Sample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/MozaChloSample.png b/packages/alphatab/test-data/musicxml-samples/MozaChloSample.png index 3aeb1edbe..48fd0b23d 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/MozaChloSample.png and b/packages/alphatab/test-data/musicxml-samples/MozaChloSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/MozaVeilSample.png b/packages/alphatab/test-data/musicxml-samples/MozaVeilSample.png index 39ac07349..f317aecfe 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/MozaVeilSample.png and b/packages/alphatab/test-data/musicxml-samples/MozaVeilSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/MozartTrio.png b/packages/alphatab/test-data/musicxml-samples/MozartTrio.png index 5081ac08b..1ac3726c8 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/MozartTrio.png and b/packages/alphatab/test-data/musicxml-samples/MozartTrio.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/Saltarello.png b/packages/alphatab/test-data/musicxml-samples/Saltarello.png index 4684f76cb..462d629ad 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/Saltarello.png and b/packages/alphatab/test-data/musicxml-samples/Saltarello.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/SchbAvMaSample.png b/packages/alphatab/test-data/musicxml-samples/SchbAvMaSample.png index 8e03eff88..58f000ecf 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/SchbAvMaSample.png and b/packages/alphatab/test-data/musicxml-samples/SchbAvMaSample.png differ diff --git a/packages/alphatab/test-data/musicxml-samples/Telemann.png b/packages/alphatab/test-data/musicxml-samples/Telemann.png index 166555266..84aa6e18d 100644 Binary files a/packages/alphatab/test-data/musicxml-samples/Telemann.png and b/packages/alphatab/test-data/musicxml-samples/Telemann.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/01a-Pitches-Pitches.png b/packages/alphatab/test-data/musicxml-testsuite/01a-Pitches-Pitches.png index 177d85257..485ce018f 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/01a-Pitches-Pitches.png and b/packages/alphatab/test-data/musicxml-testsuite/01a-Pitches-Pitches.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/01b-Pitches-Intervals.png b/packages/alphatab/test-data/musicxml-testsuite/01b-Pitches-Intervals.png index adadef622..8cb204dbd 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/01b-Pitches-Intervals.png and b/packages/alphatab/test-data/musicxml-testsuite/01b-Pitches-Intervals.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/01c-Pitches-NoVoiceElement.png b/packages/alphatab/test-data/musicxml-testsuite/01c-Pitches-NoVoiceElement.png index 6b5920f81..f1a2a6d37 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/01c-Pitches-NoVoiceElement.png and b/packages/alphatab/test-data/musicxml-testsuite/01c-Pitches-NoVoiceElement.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/01e-Pitches-ParenthesizedAccidentals.png b/packages/alphatab/test-data/musicxml-testsuite/01e-Pitches-ParenthesizedAccidentals.png index 3a3303683..bf02f3aa4 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/01e-Pitches-ParenthesizedAccidentals.png and b/packages/alphatab/test-data/musicxml-testsuite/01e-Pitches-ParenthesizedAccidentals.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/01f-Pitches-ParenthesizedMicrotoneAccidentals.png b/packages/alphatab/test-data/musicxml-testsuite/01f-Pitches-ParenthesizedMicrotoneAccidentals.png index 16a69b6b2..d80cbec2d 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/01f-Pitches-ParenthesizedMicrotoneAccidentals.png and b/packages/alphatab/test-data/musicxml-testsuite/01f-Pitches-ParenthesizedMicrotoneAccidentals.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/02a-Rests-Durations.png b/packages/alphatab/test-data/musicxml-testsuite/02a-Rests-Durations.png index 2ce986fdc..4afc12740 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/02a-Rests-Durations.png and b/packages/alphatab/test-data/musicxml-testsuite/02a-Rests-Durations.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/02b-Rests-PitchedRests.png b/packages/alphatab/test-data/musicxml-testsuite/02b-Rests-PitchedRests.png index 517927c22..eb17ff8c4 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/02b-Rests-PitchedRests.png and b/packages/alphatab/test-data/musicxml-testsuite/02b-Rests-PitchedRests.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/02c-Rests-MultiMeasureRests.png b/packages/alphatab/test-data/musicxml-testsuite/02c-Rests-MultiMeasureRests.png index bd43ba84a..66c2a5ed7 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/02c-Rests-MultiMeasureRests.png and b/packages/alphatab/test-data/musicxml-testsuite/02c-Rests-MultiMeasureRests.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/02d-Rests-Multimeasure-TimeSignatures.png b/packages/alphatab/test-data/musicxml-testsuite/02d-Rests-Multimeasure-TimeSignatures.png index fedb0ed75..1a7354c07 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/02d-Rests-Multimeasure-TimeSignatures.png and b/packages/alphatab/test-data/musicxml-testsuite/02d-Rests-Multimeasure-TimeSignatures.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/02e-Rests-NoType.png b/packages/alphatab/test-data/musicxml-testsuite/02e-Rests-NoType.png index 3afc8635d..a82a5499e 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/02e-Rests-NoType.png and b/packages/alphatab/test-data/musicxml-testsuite/02e-Rests-NoType.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/03a-Rhythm-Durations.png b/packages/alphatab/test-data/musicxml-testsuite/03a-Rhythm-Durations.png index 44aec70d9..639fa1c34 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/03a-Rhythm-Durations.png and b/packages/alphatab/test-data/musicxml-testsuite/03a-Rhythm-Durations.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/03c-Rhythm-DivisionChange.png b/packages/alphatab/test-data/musicxml-testsuite/03c-Rhythm-DivisionChange.png index 89d612723..e186d2cc0 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/03c-Rhythm-DivisionChange.png and b/packages/alphatab/test-data/musicxml-testsuite/03c-Rhythm-DivisionChange.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/03d-Rhythm-DottedDurations-Factors.png b/packages/alphatab/test-data/musicxml-testsuite/03d-Rhythm-DottedDurations-Factors.png index 0f34f66b9..345859786 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/03d-Rhythm-DottedDurations-Factors.png and b/packages/alphatab/test-data/musicxml-testsuite/03d-Rhythm-DottedDurations-Factors.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/03e-Rhythm-No-Divisions.png b/packages/alphatab/test-data/musicxml-testsuite/03e-Rhythm-No-Divisions.png index 8408e0440..877359414 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/03e-Rhythm-No-Divisions.png and b/packages/alphatab/test-data/musicxml-testsuite/03e-Rhythm-No-Divisions.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/03f-Rhythm-Forward.png b/packages/alphatab/test-data/musicxml-testsuite/03f-Rhythm-Forward.png index fc2530121..64432f41f 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/03f-Rhythm-Forward.png and b/packages/alphatab/test-data/musicxml-testsuite/03f-Rhythm-Forward.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11a-TimeSignatures.png b/packages/alphatab/test-data/musicxml-testsuite/11a-TimeSignatures.png index 6cb2e6039..f9c027803 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11a-TimeSignatures.png and b/packages/alphatab/test-data/musicxml-testsuite/11a-TimeSignatures.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11b-TimeSignatures-NoTime.png b/packages/alphatab/test-data/musicxml-testsuite/11b-TimeSignatures-NoTime.png index a24eb5115..72bcc66c8 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11b-TimeSignatures-NoTime.png and b/packages/alphatab/test-data/musicxml-testsuite/11b-TimeSignatures-NoTime.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11c-TimeSignatures-CompoundSimple.png b/packages/alphatab/test-data/musicxml-testsuite/11c-TimeSignatures-CompoundSimple.png index 974e10d2c..bb4b0795b 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11c-TimeSignatures-CompoundSimple.png and b/packages/alphatab/test-data/musicxml-testsuite/11c-TimeSignatures-CompoundSimple.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11d-TimeSignatures-CompoundMultiple.png b/packages/alphatab/test-data/musicxml-testsuite/11d-TimeSignatures-CompoundMultiple.png index be8694844..c3ad65704 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11d-TimeSignatures-CompoundMultiple.png and b/packages/alphatab/test-data/musicxml-testsuite/11d-TimeSignatures-CompoundMultiple.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11e-TimeSignatures-CompoundMixed.png b/packages/alphatab/test-data/musicxml-testsuite/11e-TimeSignatures-CompoundMixed.png index b926aae1d..f808e10c9 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11e-TimeSignatures-CompoundMixed.png and b/packages/alphatab/test-data/musicxml-testsuite/11e-TimeSignatures-CompoundMixed.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11f-TimeSignatures-SymbolMeaning.png b/packages/alphatab/test-data/musicxml-testsuite/11f-TimeSignatures-SymbolMeaning.png index 600b66cfe..ecda9e2c8 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11f-TimeSignatures-SymbolMeaning.png and b/packages/alphatab/test-data/musicxml-testsuite/11f-TimeSignatures-SymbolMeaning.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11g-TimeSignatures-SingleNumber.png b/packages/alphatab/test-data/musicxml-testsuite/11g-TimeSignatures-SingleNumber.png index c89b80763..f0c606a00 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11g-TimeSignatures-SingleNumber.png and b/packages/alphatab/test-data/musicxml-testsuite/11g-TimeSignatures-SingleNumber.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/11h-TimeSignatures-SenzaMisura.png b/packages/alphatab/test-data/musicxml-testsuite/11h-TimeSignatures-SenzaMisura.png index 3a695a90e..1f452700c 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/11h-TimeSignatures-SenzaMisura.png and b/packages/alphatab/test-data/musicxml-testsuite/11h-TimeSignatures-SenzaMisura.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/12a-Clefs.png b/packages/alphatab/test-data/musicxml-testsuite/12a-Clefs.png index c6ebaf8a2..e09793f1e 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/12a-Clefs.png and b/packages/alphatab/test-data/musicxml-testsuite/12a-Clefs.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/12b-Clefs-NoKeyOrClef.png b/packages/alphatab/test-data/musicxml-testsuite/12b-Clefs-NoKeyOrClef.png index 1e7fca084..aeba1d346 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/12b-Clefs-NoKeyOrClef.png and b/packages/alphatab/test-data/musicxml-testsuite/12b-Clefs-NoKeyOrClef.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/13a-KeySignatures.png b/packages/alphatab/test-data/musicxml-testsuite/13a-KeySignatures.png index 77a288384..c28ceb410 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/13a-KeySignatures.png and b/packages/alphatab/test-data/musicxml-testsuite/13a-KeySignatures.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/13b-KeySignatures-ChurchModes.png b/packages/alphatab/test-data/musicxml-testsuite/13b-KeySignatures-ChurchModes.png index ed8af15b6..1ba6e56de 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/13b-KeySignatures-ChurchModes.png and b/packages/alphatab/test-data/musicxml-testsuite/13b-KeySignatures-ChurchModes.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/13c-KeySignatures-NonTraditional.png b/packages/alphatab/test-data/musicxml-testsuite/13c-KeySignatures-NonTraditional.png index 8f325d69e..efafe8045 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/13c-KeySignatures-NonTraditional.png and b/packages/alphatab/test-data/musicxml-testsuite/13c-KeySignatures-NonTraditional.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/13d-KeySignatures-Microtones.png b/packages/alphatab/test-data/musicxml-testsuite/13d-KeySignatures-Microtones.png index c43b108d8..5fd9b7097 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/13d-KeySignatures-Microtones.png and b/packages/alphatab/test-data/musicxml-testsuite/13d-KeySignatures-Microtones.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/13e-KeySignatures-Cancel.png b/packages/alphatab/test-data/musicxml-testsuite/13e-KeySignatures-Cancel.png index 776b11fbb..3ff305136 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/13e-KeySignatures-Cancel.png and b/packages/alphatab/test-data/musicxml-testsuite/13e-KeySignatures-Cancel.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/13f-KeySignatures-Visible.png b/packages/alphatab/test-data/musicxml-testsuite/13f-KeySignatures-Visible.png index 3f219fced..b9c2d723b 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/13f-KeySignatures-Visible.png and b/packages/alphatab/test-data/musicxml-testsuite/13f-KeySignatures-Visible.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/14a-StaffDetails-LineChanges.png b/packages/alphatab/test-data/musicxml-testsuite/14a-StaffDetails-LineChanges.png index d4f514aee..77e1536a4 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/14a-StaffDetails-LineChanges.png and b/packages/alphatab/test-data/musicxml-testsuite/14a-StaffDetails-LineChanges.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/21c-Chords-ThreeNotesDuration.png b/packages/alphatab/test-data/musicxml-testsuite/21c-Chords-ThreeNotesDuration.png index 2b16af82a..bcb6ab34c 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/21c-Chords-ThreeNotesDuration.png and b/packages/alphatab/test-data/musicxml-testsuite/21c-Chords-ThreeNotesDuration.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/21d-Chords-SchubertStabatMater.png b/packages/alphatab/test-data/musicxml-testsuite/21d-Chords-SchubertStabatMater.png index 7188c59ff..6ae089e90 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/21d-Chords-SchubertStabatMater.png and b/packages/alphatab/test-data/musicxml-testsuite/21d-Chords-SchubertStabatMater.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/21f-Chord-ElementInBetween.png b/packages/alphatab/test-data/musicxml-testsuite/21f-Chord-ElementInBetween.png index 9d2820959..8c1f7e0e5 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/21f-Chord-ElementInBetween.png and b/packages/alphatab/test-data/musicxml-testsuite/21f-Chord-ElementInBetween.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/21g-Chords-Tremolos.png b/packages/alphatab/test-data/musicxml-testsuite/21g-Chords-Tremolos.png index 0dd679c73..873f419e7 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/21g-Chords-Tremolos.png and b/packages/alphatab/test-data/musicxml-testsuite/21g-Chords-Tremolos.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/21h-Chord-Accidentals.png b/packages/alphatab/test-data/musicxml-testsuite/21h-Chord-Accidentals.png index d33d4e484..487994dd7 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/21h-Chord-Accidentals.png and b/packages/alphatab/test-data/musicxml-testsuite/21h-Chord-Accidentals.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/22a-Noteheads.png b/packages/alphatab/test-data/musicxml-testsuite/22a-Noteheads.png index f0081de94..22ebe92ce 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/22a-Noteheads.png and b/packages/alphatab/test-data/musicxml-testsuite/22a-Noteheads.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/22b-Staff-Notestyles.png b/packages/alphatab/test-data/musicxml-testsuite/22b-Staff-Notestyles.png index 1faf1f230..21c529ada 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/22b-Staff-Notestyles.png and b/packages/alphatab/test-data/musicxml-testsuite/22b-Staff-Notestyles.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/22c-Noteheads-Chords.png b/packages/alphatab/test-data/musicxml-testsuite/22c-Noteheads-Chords.png index 5d0ba7ff2..5112d9377 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/22c-Noteheads-Chords.png and b/packages/alphatab/test-data/musicxml-testsuite/22c-Noteheads-Chords.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/23a-Tuplets.png b/packages/alphatab/test-data/musicxml-testsuite/23a-Tuplets.png index 106175ae5..006a7b981 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/23a-Tuplets.png and b/packages/alphatab/test-data/musicxml-testsuite/23a-Tuplets.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/23b-Tuplets-Styles.png b/packages/alphatab/test-data/musicxml-testsuite/23b-Tuplets-Styles.png index d664de55e..a8227d5ef 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/23b-Tuplets-Styles.png and b/packages/alphatab/test-data/musicxml-testsuite/23b-Tuplets-Styles.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/23c-Tuplet-Display-NonStandard.png b/packages/alphatab/test-data/musicxml-testsuite/23c-Tuplet-Display-NonStandard.png index c2a7e0089..d1c9aba82 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/23c-Tuplet-Display-NonStandard.png and b/packages/alphatab/test-data/musicxml-testsuite/23c-Tuplet-Display-NonStandard.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/23d-Tuplets-Nested.png b/packages/alphatab/test-data/musicxml-testsuite/23d-Tuplets-Nested.png index b831ad7f2..48a641f4f 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/23d-Tuplets-Nested.png and b/packages/alphatab/test-data/musicxml-testsuite/23d-Tuplets-Nested.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/23e-Tuplets-Tremolo.png b/packages/alphatab/test-data/musicxml-testsuite/23e-Tuplets-Tremolo.png index 671b59043..f50edf86e 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/23e-Tuplets-Tremolo.png and b/packages/alphatab/test-data/musicxml-testsuite/23e-Tuplets-Tremolo.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/23f-Tuplets-DurationButNoBracket.png b/packages/alphatab/test-data/musicxml-testsuite/23f-Tuplets-DurationButNoBracket.png index 47f1c835a..627ee51f4 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/23f-Tuplets-DurationButNoBracket.png and b/packages/alphatab/test-data/musicxml-testsuite/23f-Tuplets-DurationButNoBracket.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/24a-GraceNotes.png b/packages/alphatab/test-data/musicxml-testsuite/24a-GraceNotes.png index aa74e64d8..bd15a4f77 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/24a-GraceNotes.png and b/packages/alphatab/test-data/musicxml-testsuite/24a-GraceNotes.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/24c-GraceNote-MeasureEnd.png b/packages/alphatab/test-data/musicxml-testsuite/24c-GraceNote-MeasureEnd.png index 8f395d247..a314ab999 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/24c-GraceNote-MeasureEnd.png and b/packages/alphatab/test-data/musicxml-testsuite/24c-GraceNote-MeasureEnd.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/24e-GraceNote-StaffChange.png b/packages/alphatab/test-data/musicxml-testsuite/24e-GraceNote-StaffChange.png index b91699bc2..bb51dd32e 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/24e-GraceNote-StaffChange.png and b/packages/alphatab/test-data/musicxml-testsuite/24e-GraceNote-StaffChange.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/24f-GraceNote-Slur.png b/packages/alphatab/test-data/musicxml-testsuite/24f-GraceNote-Slur.png index 1a7630dd9..f2b6263a1 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/24f-GraceNote-Slur.png and b/packages/alphatab/test-data/musicxml-testsuite/24f-GraceNote-Slur.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/31a-Directions.png b/packages/alphatab/test-data/musicxml-testsuite/31a-Directions.png index 7b3d17e63..919006a66 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/31a-Directions.png and b/packages/alphatab/test-data/musicxml-testsuite/31a-Directions.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/31b-Directions-Order.png b/packages/alphatab/test-data/musicxml-testsuite/31b-Directions-Order.png index b0e4259f4..572747c47 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/31b-Directions-Order.png and b/packages/alphatab/test-data/musicxml-testsuite/31b-Directions-Order.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/31d-Directions-Compounds.png b/packages/alphatab/test-data/musicxml-testsuite/31d-Directions-Compounds.png index 96fe71ee5..79015d945 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/31d-Directions-Compounds.png and b/packages/alphatab/test-data/musicxml-testsuite/31d-Directions-Compounds.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/32a-Notations.png b/packages/alphatab/test-data/musicxml-testsuite/32a-Notations.png index a00ba3205..e37835593 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/32a-Notations.png and b/packages/alphatab/test-data/musicxml-testsuite/32a-Notations.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/32b-Articulations-Texts.png b/packages/alphatab/test-data/musicxml-testsuite/32b-Articulations-Texts.png index a71377b7e..baea4cf18 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/32b-Articulations-Texts.png and b/packages/alphatab/test-data/musicxml-testsuite/32b-Articulations-Texts.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/32d-Arpeggio.png b/packages/alphatab/test-data/musicxml-testsuite/32d-Arpeggio.png index 0fcb2cb12..8a345a5d2 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/32d-Arpeggio.png and b/packages/alphatab/test-data/musicxml-testsuite/32d-Arpeggio.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33a-Spanners.png b/packages/alphatab/test-data/musicxml-testsuite/33a-Spanners.png index 8e0d7c4ca..15eb345ef 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33a-Spanners.png and b/packages/alphatab/test-data/musicxml-testsuite/33a-Spanners.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33b-Spanners-Tie.png b/packages/alphatab/test-data/musicxml-testsuite/33b-Spanners-Tie.png index 8962f5a1b..51b069802 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33b-Spanners-Tie.png and b/packages/alphatab/test-data/musicxml-testsuite/33b-Spanners-Tie.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33da-Spanners-OctaveShifts-before.png b/packages/alphatab/test-data/musicxml-testsuite/33da-Spanners-OctaveShifts-before.png index 5fa9e3a92..a97415271 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33da-Spanners-OctaveShifts-before.png and b/packages/alphatab/test-data/musicxml-testsuite/33da-Spanners-OctaveShifts-before.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33db-Spanners-OctaveShifts-after.png b/packages/alphatab/test-data/musicxml-testsuite/33db-Spanners-OctaveShifts-after.png index 22497714f..1f7ba3616 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33db-Spanners-OctaveShifts-after.png and b/packages/alphatab/test-data/musicxml-testsuite/33db-Spanners-OctaveShifts-after.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33e-Spanners-OctaveShifts-InvalidSize.png b/packages/alphatab/test-data/musicxml-testsuite/33e-Spanners-OctaveShifts-InvalidSize.png index 5a8839501..800bad82a 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33e-Spanners-OctaveShifts-InvalidSize.png and b/packages/alphatab/test-data/musicxml-testsuite/33e-Spanners-OctaveShifts-InvalidSize.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33f-Trill-EndingOnGraceNote.png b/packages/alphatab/test-data/musicxml-testsuite/33f-Trill-EndingOnGraceNote.png index b5c660870..ee191306f 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33f-Trill-EndingOnGraceNote.png and b/packages/alphatab/test-data/musicxml-testsuite/33f-Trill-EndingOnGraceNote.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33h-Spanners-Glissando.png b/packages/alphatab/test-data/musicxml-testsuite/33h-Spanners-Glissando.png index 5a19f5e29..0b6eca49d 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33h-Spanners-Glissando.png and b/packages/alphatab/test-data/musicxml-testsuite/33h-Spanners-Glissando.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33i-Ties-NotEnded.png b/packages/alphatab/test-data/musicxml-testsuite/33i-Ties-NotEnded.png index a383c7b2b..9eded3a9d 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33i-Ties-NotEnded.png and b/packages/alphatab/test-data/musicxml-testsuite/33i-Ties-NotEnded.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/33j-Beams-Tremolos.png b/packages/alphatab/test-data/musicxml-testsuite/33j-Beams-Tremolos.png index 69f95779c..bd1afc583 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/33j-Beams-Tremolos.png and b/packages/alphatab/test-data/musicxml-testsuite/33j-Beams-Tremolos.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/34a-Print-Object-Spanners.png b/packages/alphatab/test-data/musicxml-testsuite/34a-Print-Object-Spanners.png index 95fca954b..2af61c5cd 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/34a-Print-Object-Spanners.png and b/packages/alphatab/test-data/musicxml-testsuite/34a-Print-Object-Spanners.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/34b-Colors.png b/packages/alphatab/test-data/musicxml-testsuite/34b-Colors.png index ef1854676..5e3b70f45 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/34b-Colors.png and b/packages/alphatab/test-data/musicxml-testsuite/34b-Colors.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/34c-Font-Size.png b/packages/alphatab/test-data/musicxml-testsuite/34c-Font-Size.png index 2bd905f44..584d98317 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/34c-Font-Size.png and b/packages/alphatab/test-data/musicxml-testsuite/34c-Font-Size.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41a-MultiParts-Partorder.png b/packages/alphatab/test-data/musicxml-testsuite/41a-MultiParts-Partorder.png index 6571c5076..3c1d45012 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41a-MultiParts-Partorder.png and b/packages/alphatab/test-data/musicxml-testsuite/41a-MultiParts-Partorder.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41b-MultiParts-MoreThan10.png b/packages/alphatab/test-data/musicxml-testsuite/41b-MultiParts-MoreThan10.png index fcab0e37e..e326c5d0b 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41b-MultiParts-MoreThan10.png and b/packages/alphatab/test-data/musicxml-testsuite/41b-MultiParts-MoreThan10.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41c-StaffGroups.png b/packages/alphatab/test-data/musicxml-testsuite/41c-StaffGroups.png index bb22d7fcd..c867b41d4 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41c-StaffGroups.png and b/packages/alphatab/test-data/musicxml-testsuite/41c-StaffGroups.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41d-StaffGroups-Nested.png b/packages/alphatab/test-data/musicxml-testsuite/41d-StaffGroups-Nested.png index 2dc06d360..3c234f040 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41d-StaffGroups-Nested.png and b/packages/alphatab/test-data/musicxml-testsuite/41d-StaffGroups-Nested.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41e-StaffGroups-InstrumentNames-Linebroken.png b/packages/alphatab/test-data/musicxml-testsuite/41e-StaffGroups-InstrumentNames-Linebroken.png index 334b34fcb..4d947a325 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41e-StaffGroups-InstrumentNames-Linebroken.png and b/packages/alphatab/test-data/musicxml-testsuite/41e-StaffGroups-InstrumentNames-Linebroken.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41f-StaffGroups-Overlapping.png b/packages/alphatab/test-data/musicxml-testsuite/41f-StaffGroups-Overlapping.png index 7e16f7b2b..d9d67d2b0 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41f-StaffGroups-Overlapping.png and b/packages/alphatab/test-data/musicxml-testsuite/41f-StaffGroups-Overlapping.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41g-StaffGroups-NestingOrder.png b/packages/alphatab/test-data/musicxml-testsuite/41g-StaffGroups-NestingOrder.png index 65e2f7336..dbf7d287c 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41g-StaffGroups-NestingOrder.png and b/packages/alphatab/test-data/musicxml-testsuite/41g-StaffGroups-NestingOrder.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41h-TooManyParts.png b/packages/alphatab/test-data/musicxml-testsuite/41h-TooManyParts.png index 124044ac9..24e5101f0 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41h-TooManyParts.png and b/packages/alphatab/test-data/musicxml-testsuite/41h-TooManyParts.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41i-PartNameDisplay-Override.png b/packages/alphatab/test-data/musicxml-testsuite/41i-PartNameDisplay-Override.png index 492fdfd03..89894ecf4 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41i-PartNameDisplay-Override.png and b/packages/alphatab/test-data/musicxml-testsuite/41i-PartNameDisplay-Override.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41j-PartNameDisplay-Multiple-DisplayText-Children.png b/packages/alphatab/test-data/musicxml-testsuite/41j-PartNameDisplay-Multiple-DisplayText-Children.png index 682530666..aacb8d8b8 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41j-PartNameDisplay-Multiple-DisplayText-Children.png and b/packages/alphatab/test-data/musicxml-testsuite/41j-PartNameDisplay-Multiple-DisplayText-Children.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41k-PartName-Print.png b/packages/alphatab/test-data/musicxml-testsuite/41k-PartName-Print.png index 56fcdbdaa..5ecaaf6e3 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41k-PartName-Print.png and b/packages/alphatab/test-data/musicxml-testsuite/41k-PartName-Print.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/41l-GroupNameDisplay-Override.png b/packages/alphatab/test-data/musicxml-testsuite/41l-GroupNameDisplay-Override.png index 246d52c2b..b0f3418ef 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/41l-GroupNameDisplay-Override.png and b/packages/alphatab/test-data/musicxml-testsuite/41l-GroupNameDisplay-Override.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/42a-MultiVoice-TwoVoicesOnStaff-Lyrics.png b/packages/alphatab/test-data/musicxml-testsuite/42a-MultiVoice-TwoVoicesOnStaff-Lyrics.png index c7c77389f..79c2ebd51 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/42a-MultiVoice-TwoVoicesOnStaff-Lyrics.png and b/packages/alphatab/test-data/musicxml-testsuite/42a-MultiVoice-TwoVoicesOnStaff-Lyrics.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/42b-MultiVoice-MidMeasureClefChange.png b/packages/alphatab/test-data/musicxml-testsuite/42b-MultiVoice-MidMeasureClefChange.png index a1c930f91..64b44b953 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/42b-MultiVoice-MidMeasureClefChange.png and b/packages/alphatab/test-data/musicxml-testsuite/42b-MultiVoice-MidMeasureClefChange.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/43a-PianoStaff.png b/packages/alphatab/test-data/musicxml-testsuite/43a-PianoStaff.png index 55734931a..a122e6e00 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/43a-PianoStaff.png and b/packages/alphatab/test-data/musicxml-testsuite/43a-PianoStaff.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/43b-MultiStaff-DifferentKeys.png b/packages/alphatab/test-data/musicxml-testsuite/43b-MultiStaff-DifferentKeys.png index 1404d3b64..212d73082 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/43b-MultiStaff-DifferentKeys.png and b/packages/alphatab/test-data/musicxml-testsuite/43b-MultiStaff-DifferentKeys.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/43c-MultiStaff-DifferentKeysAfterBackup.png b/packages/alphatab/test-data/musicxml-testsuite/43c-MultiStaff-DifferentKeysAfterBackup.png index adae8441e..fac6fe6a6 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/43c-MultiStaff-DifferentKeysAfterBackup.png and b/packages/alphatab/test-data/musicxml-testsuite/43c-MultiStaff-DifferentKeysAfterBackup.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/43d-MultiStaff-StaffChange.png b/packages/alphatab/test-data/musicxml-testsuite/43d-MultiStaff-StaffChange.png index d47e7d5c1..9f209cbff 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/43d-MultiStaff-StaffChange.png and b/packages/alphatab/test-data/musicxml-testsuite/43d-MultiStaff-StaffChange.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/43e-Multistaff-ClefDynamics.png b/packages/alphatab/test-data/musicxml-testsuite/43e-Multistaff-ClefDynamics.png index 02611b7b5..3c6e797bd 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/43e-Multistaff-ClefDynamics.png and b/packages/alphatab/test-data/musicxml-testsuite/43e-Multistaff-ClefDynamics.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/43f-MultiStaff-Lyrics.png b/packages/alphatab/test-data/musicxml-testsuite/43f-MultiStaff-Lyrics.png index 6f21220a2..393c63ea5 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/43f-MultiStaff-Lyrics.png and b/packages/alphatab/test-data/musicxml-testsuite/43f-MultiStaff-Lyrics.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/43g-MultiStaff-PartSymbol.png b/packages/alphatab/test-data/musicxml-testsuite/43g-MultiStaff-PartSymbol.png index 52cdf1584..93eabf63e 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/43g-MultiStaff-PartSymbol.png and b/packages/alphatab/test-data/musicxml-testsuite/43g-MultiStaff-PartSymbol.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45a-SimpleRepeat.png b/packages/alphatab/test-data/musicxml-testsuite/45a-SimpleRepeat.png index d930e24b2..0ee9bf909 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45a-SimpleRepeat.png and b/packages/alphatab/test-data/musicxml-testsuite/45a-SimpleRepeat.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45b-RepeatWithAlternatives.png b/packages/alphatab/test-data/musicxml-testsuite/45b-RepeatWithAlternatives.png index 25f256c4e..71f741d7b 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45b-RepeatWithAlternatives.png and b/packages/alphatab/test-data/musicxml-testsuite/45b-RepeatWithAlternatives.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45c-SimpleRepeat-Nested.png b/packages/alphatab/test-data/musicxml-testsuite/45c-SimpleRepeat-Nested.png index 0498d4530..2a90fb703 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45c-SimpleRepeat-Nested.png and b/packages/alphatab/test-data/musicxml-testsuite/45c-SimpleRepeat-Nested.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45d-Repeats-MultipleEndings.png b/packages/alphatab/test-data/musicxml-testsuite/45d-Repeats-MultipleEndings.png index 67f4aef4f..c4ea03896 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45d-Repeats-MultipleEndings.png and b/packages/alphatab/test-data/musicxml-testsuite/45d-Repeats-MultipleEndings.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45e-Repeats-Combination.png b/packages/alphatab/test-data/musicxml-testsuite/45e-Repeats-Combination.png index 573bd1c7c..00cd3301b 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45e-Repeats-Combination.png and b/packages/alphatab/test-data/musicxml-testsuite/45e-Repeats-Combination.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45f-Repeats-InvalidEndings.png b/packages/alphatab/test-data/musicxml-testsuite/45f-Repeats-InvalidEndings.png index 517656553..1790c2ca0 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45f-Repeats-InvalidEndings.png and b/packages/alphatab/test-data/musicxml-testsuite/45f-Repeats-InvalidEndings.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45g-Repeats-NotEnded.png b/packages/alphatab/test-data/musicxml-testsuite/45g-Repeats-NotEnded.png index 7c54e4df7..57d1b0739 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45g-Repeats-NotEnded.png and b/packages/alphatab/test-data/musicxml-testsuite/45g-Repeats-NotEnded.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45h-Repeats-Partial.png b/packages/alphatab/test-data/musicxml-testsuite/45h-Repeats-Partial.png index 7eb772b5c..73791f19d 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45h-Repeats-Partial.png and b/packages/alphatab/test-data/musicxml-testsuite/45h-Repeats-Partial.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/45i-Repeats-Nested.png b/packages/alphatab/test-data/musicxml-testsuite/45i-Repeats-Nested.png index 75cd736ab..93bffe2c6 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/45i-Repeats-Nested.png and b/packages/alphatab/test-data/musicxml-testsuite/45i-Repeats-Nested.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/46a-Barlines.png b/packages/alphatab/test-data/musicxml-testsuite/46a-Barlines.png index 34fdc33b7..43191c8be 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/46a-Barlines.png and b/packages/alphatab/test-data/musicxml-testsuite/46a-Barlines.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/46c-Midmeasure-Clef.png b/packages/alphatab/test-data/musicxml-testsuite/46c-Midmeasure-Clef.png index dc9ce78f2..551196664 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/46c-Midmeasure-Clef.png and b/packages/alphatab/test-data/musicxml-testsuite/46c-Midmeasure-Clef.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/46d-PickupMeasure-ImplicitMeasures.png b/packages/alphatab/test-data/musicxml-testsuite/46d-PickupMeasure-ImplicitMeasures.png index f69622f3e..4e882fe56 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/46d-PickupMeasure-ImplicitMeasures.png and b/packages/alphatab/test-data/musicxml-testsuite/46d-PickupMeasure-ImplicitMeasures.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/46g-PickupMeasure-Chordnames-FiguredBass.png b/packages/alphatab/test-data/musicxml-testsuite/46g-PickupMeasure-Chordnames-FiguredBass.png index 08e19a3b3..754eb9ac6 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/46g-PickupMeasure-Chordnames-FiguredBass.png and b/packages/alphatab/test-data/musicxml-testsuite/46g-PickupMeasure-Chordnames-FiguredBass.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/51b-Header-Quotes.png b/packages/alphatab/test-data/musicxml-testsuite/51b-Header-Quotes.png index 5900bce6e..4247a2034 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/51b-Header-Quotes.png and b/packages/alphatab/test-data/musicxml-testsuite/51b-Header-Quotes.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/51c-MultipleRights.png b/packages/alphatab/test-data/musicxml-testsuite/51c-MultipleRights.png index 462b846a5..5d9cf3b18 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/51c-MultipleRights.png and b/packages/alphatab/test-data/musicxml-testsuite/51c-MultipleRights.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/51d-EmptyTitle.png b/packages/alphatab/test-data/musicxml-testsuite/51d-EmptyTitle.png index 20b9bfdff..5de169415 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/51d-EmptyTitle.png and b/packages/alphatab/test-data/musicxml-testsuite/51d-EmptyTitle.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/52a-PageLayout.png b/packages/alphatab/test-data/musicxml-testsuite/52a-PageLayout.png index e43de98fb..9c6233650 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/52a-PageLayout.png and b/packages/alphatab/test-data/musicxml-testsuite/52a-PageLayout.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/52b-Breaks.png b/packages/alphatab/test-data/musicxml-testsuite/52b-Breaks.png index 718db35e5..0a8f76acc 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/52b-Breaks.png and b/packages/alphatab/test-data/musicxml-testsuite/52b-Breaks.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/61a-Lyrics.png b/packages/alphatab/test-data/musicxml-testsuite/61a-Lyrics.png index 06929b92f..404a791a3 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/61a-Lyrics.png and b/packages/alphatab/test-data/musicxml-testsuite/61a-Lyrics.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/61b-MultipleLyrics.png b/packages/alphatab/test-data/musicxml-testsuite/61b-MultipleLyrics.png index 1df1f587a..4ea78e394 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/61b-MultipleLyrics.png and b/packages/alphatab/test-data/musicxml-testsuite/61b-MultipleLyrics.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/61g-Lyrics-NameNumber.png b/packages/alphatab/test-data/musicxml-testsuite/61g-Lyrics-NameNumber.png index 02d30df63..8a32bb16f 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/61g-Lyrics-NameNumber.png and b/packages/alphatab/test-data/musicxml-testsuite/61g-Lyrics-NameNumber.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/61h-Lyrics-BeamsMelismata.png b/packages/alphatab/test-data/musicxml-testsuite/61h-Lyrics-BeamsMelismata.png index e7487ec90..3832925fe 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/61h-Lyrics-BeamsMelismata.png and b/packages/alphatab/test-data/musicxml-testsuite/61h-Lyrics-BeamsMelismata.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/61j-Lyrics-Elisions.png b/packages/alphatab/test-data/musicxml-testsuite/61j-Lyrics-Elisions.png index af978c8fa..74169f195 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/61j-Lyrics-Elisions.png and b/packages/alphatab/test-data/musicxml-testsuite/61j-Lyrics-Elisions.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/71e-TabStaves.png b/packages/alphatab/test-data/musicxml-testsuite/71e-TabStaves.png index dc8cd4760..769c09d7d 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/71e-TabStaves.png and b/packages/alphatab/test-data/musicxml-testsuite/71e-TabStaves.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/71f-AllChordTypes.png b/packages/alphatab/test-data/musicxml-testsuite/71f-AllChordTypes.png index b3246dc73..3f4417bd0 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/71f-AllChordTypes.png and b/packages/alphatab/test-data/musicxml-testsuite/71f-AllChordTypes.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/71g-MultipleChordnames.png b/packages/alphatab/test-data/musicxml-testsuite/71g-MultipleChordnames.png index ceaa94f50..febf99c79 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/71g-MultipleChordnames.png and b/packages/alphatab/test-data/musicxml-testsuite/71g-MultipleChordnames.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/72b-TransposingInstruments-Full.png b/packages/alphatab/test-data/musicxml-testsuite/72b-TransposingInstruments-Full.png index 4300fb626..f400ec349 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/72b-TransposingInstruments-Full.png and b/packages/alphatab/test-data/musicxml-testsuite/72b-TransposingInstruments-Full.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/72c-TransposingInstruments-Change.png b/packages/alphatab/test-data/musicxml-testsuite/72c-TransposingInstruments-Change.png index 21a832d6f..546eeb788 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/72c-TransposingInstruments-Change.png and b/packages/alphatab/test-data/musicxml-testsuite/72c-TransposingInstruments-Change.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/73a-Percussion.png b/packages/alphatab/test-data/musicxml-testsuite/73a-Percussion.png index b485009b6..6c80a2f56 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/73a-Percussion.png and b/packages/alphatab/test-data/musicxml-testsuite/73a-Percussion.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/74a-FiguredBass.png b/packages/alphatab/test-data/musicxml-testsuite/74a-FiguredBass.png index a5e18d234..58b0abc97 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/74a-FiguredBass.png and b/packages/alphatab/test-data/musicxml-testsuite/74a-FiguredBass.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/99a-Sibelius5-IgnoreBeaming.png b/packages/alphatab/test-data/musicxml-testsuite/99a-Sibelius5-IgnoreBeaming.png index 3ca1abf65..bf57fa920 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/99a-Sibelius5-IgnoreBeaming.png and b/packages/alphatab/test-data/musicxml-testsuite/99a-Sibelius5-IgnoreBeaming.png differ diff --git a/packages/alphatab/test-data/musicxml-testsuite/99b-Lyrics-BeamsMelismata-IgnoreBeams.png b/packages/alphatab/test-data/musicxml-testsuite/99b-Lyrics-BeamsMelismata-IgnoreBeams.png index e7487ec90..3832925fe 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/99b-Lyrics-BeamsMelismata-IgnoreBeams.png and b/packages/alphatab/test-data/musicxml-testsuite/99b-Lyrics-BeamsMelismata-IgnoreBeams.png differ diff --git a/packages/alphatab/test-data/musicxml3/chord-diagram.png b/packages/alphatab/test-data/musicxml3/chord-diagram.png index 23edfb56b..ba59d221a 100644 Binary files a/packages/alphatab/test-data/musicxml3/chord-diagram.png and b/packages/alphatab/test-data/musicxml3/chord-diagram.png differ diff --git a/packages/alphatab/test-data/musicxml3/full-bar-rest.png b/packages/alphatab/test-data/musicxml3/full-bar-rest.png index 3b582bb42..7ad0fd551 100644 Binary files a/packages/alphatab/test-data/musicxml3/full-bar-rest.png and b/packages/alphatab/test-data/musicxml3/full-bar-rest.png differ diff --git a/packages/alphatab/test-data/musicxml3/tie-destination.png b/packages/alphatab/test-data/musicxml3/tie-destination.png index 0a7d6c204..8416ffd2b 100644 Binary files a/packages/alphatab/test-data/musicxml3/tie-destination.png and b/packages/alphatab/test-data/musicxml3/tie-destination.png differ diff --git a/packages/alphatab/test-data/musicxml3/track-volume-balance.png b/packages/alphatab/test-data/musicxml3/track-volume-balance.png index 359f54906..417c438d2 100644 Binary files a/packages/alphatab/test-data/musicxml3/track-volume-balance.png and b/packages/alphatab/test-data/musicxml3/track-volume-balance.png differ diff --git a/packages/alphatab/test-data/musicxml4/bends.png b/packages/alphatab/test-data/musicxml4/bends.png index ac7371833..78a35485b 100644 Binary files a/packages/alphatab/test-data/musicxml4/bends.png and b/packages/alphatab/test-data/musicxml4/bends.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/onnotes-beat.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/onnotes-beat.png index 1161cfd40..af3b2354d 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/onnotes-beat.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/onnotes-beat.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-bar.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-bar.png index 598ba0fa4..224a8f7dc 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-bar.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-bar.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-beat.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-beat.png index 49d2b3362..5cccaf5b1 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-beat.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-beat.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-master.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-master.png index b89766cc2..319fc96f4 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-master.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-master.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-note.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-note.png index 10b41fb99..de5f0e721 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-note.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-note.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-system.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-system.png index 2cb4c4dfa..06ab34e10 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/real-system.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/real-system.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-bar.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-bar.png index 598ba0fa4..224a8f7dc 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-bar.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-bar.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-beat.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-beat.png index f06e90c00..eaff18ea0 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-beat.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-beat.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-master.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-master.png index 0f5d63e6c..fd77b967a 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-master.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-master.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-note.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-note.png index 10b41fb99..de5f0e721 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-note.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-note.png differ diff --git a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-system.png b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-system.png index 5578287ee..5f271a32a 100644 Binary files a/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-system.png and b/packages/alphatab/test-data/visual-tests/bounds-lookup/visual-system.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/barre.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/barre.png index bda15e1a7..65065661d 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/barre.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/barre.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-slash.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-slash.png index bffe3d1dd..efe5e0522 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-slash.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-slash.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-tempo-change.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-tempo-change.png index e385c1855..18ee92da8 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-tempo-change.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-tempo-change.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-default.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-default.png index 0075f08f0..da653199d 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-default.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-songbook.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-songbook.png index 8ee181679..f5ae895c9 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-songbook.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/bend-vibrato-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/bends.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/bends.png index c09c64f66..c1060b1bd 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/bends.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/bends.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords-duplicates.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords-duplicates.png index 561df3ac6..7439ecb98 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords-duplicates.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords-duplicates.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords.png index 45308b807..ece52618f 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/chords.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/dead-slap.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/dead-slap.png index 4bdc32a6c..5fbef43fc 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/dead-slap.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/dead-slap.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-simple.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-simple.png index 1a06eab56..aa57f24ce 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-simple.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-simple.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-symbols.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-symbols.png index 9e942b3d1..d064f8f77 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-symbols.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/directions-symbols.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/fade.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/fade.png index 0c3e27c21..48b71ec58 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/fade.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/fade.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering-new.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering-new.png index 80a16bef1..4711407d2 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering-new.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering-new.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering.png index 850ae6d5e..cd0de27a6 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/fingering.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/free-time.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/free-time.png index e5abe0171..638f06901 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/free-time.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/free-time.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe-tab.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe-tab.png index 1b55d4467..f3fb01923 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe-tab.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe-tab.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe.png index 021e6d961..20fea58c1 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/golpe.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/hidden-dots.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/hidden-dots.png index d731131b3..2480948f3 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/hidden-dots.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/hidden-dots.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/hopo-arcs-at6.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/hopo-arcs-at6.png index 98790d58f..d83ea10fc 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/hopo-arcs-at6.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/hopo-arcs-at6.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/inscore-chord-diagrams.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/inscore-chord-diagrams.png index c07a31dca..f024a0d51 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/inscore-chord-diagrams.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/inscore-chord-diagrams.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/legato.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/legato.png index 2e2af5813..63b326905 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/legato.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/legato.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/let-ring.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/let-ring.png index 1478d0738..6a6cc05ec 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/let-ring.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/let-ring.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/palm-mute.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/palm-mute.png index f8f3f6add..a12fd030c 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/palm-mute.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/palm-mute.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/pick-stroke.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/pick-stroke.png index a225223f5..8f5bf4600 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/pick-stroke.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/pick-stroke.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides-line-break.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides-line-break.png index 3dad7f512..4cf09b059 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides-line-break.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides-line-break.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides.png index 945992e53..4cedf9ae9 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/slides.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/string-numbers.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/string-numbers.png index 8dff522bd..fe405030c 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/string-numbers.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/string-numbers.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-1200.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-1200.png index 859219e7f..6f35ba6a2 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-1200.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-1200.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-600.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-600.png index 1caccd0f8..618e1acab 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-600.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-600.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-850.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-850.png index 7cff47a95..f6f09afe9 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-850.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/sustain-850.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tempo-text.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tempo-text.png index 5612d41a3..e8c56111b 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tempo-text.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tempo-text.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/timer.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/timer.png index 8b0eb2d30..8e08efc44 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/timer.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/timer.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-bar.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-bar.png index 77991c34f..9f7fabd52 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-bar.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-bar.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-bottom.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-bottom.png index d71fbd0bd..41d64fa6c 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-bottom.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-bottom.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-mixed.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-mixed.png index 9466cbb47..f795b0547 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-mixed.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags-mixed.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags.png index 6433c3946..50ef5a5dd 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-flags.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-picking.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-picking.png index 6a502019c..732e5492b 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-picking.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-picking.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-beams.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-beams.png index 82b310789..3066e8dfc 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-beams.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-beams.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-flags.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-flags.png index 971aab93c..8539fa0ba 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-flags.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-buzzroll-flags.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-beams.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-beams.png index 79ecd39d9..94f219611 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-beams.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-beams.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-flags.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-flags.png index 86f8e7746..5cb89bf6b 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-flags.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-standard-default-flags.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-beams.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-beams.png index d866dc4dd..5eb8bcd51 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-beams.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-beams.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-flags.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-flags.png index 9f20f3cc9..64ecf5a34 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-flags.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-buzzroll-flags.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-beams.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-beams.png index 7a3f2259a..a47efff9a 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-beams.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-beams.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-flags.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-flags.png index 2d478d018..522308b57 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-flags.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tremolo-tabs-default-flags.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/triplet-feel.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/triplet-feel.png index ddf791d44..b86be5a58 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/triplet-feel.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/triplet-feel.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png index c675ce4ee..05284875d 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-advanced.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-huge.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-huge.png index 884d778a5..2cd79b428 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-huge.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets-huge.png differ diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets.png index f463b6c14..1c7f60f23 100644 Binary files a/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets.png and b/packages/alphatab/test-data/visual-tests/effects-and-annotations/tuplets.png differ diff --git a/packages/alphatab/test-data/visual-tests/general/colors-disabled.png b/packages/alphatab/test-data/visual-tests/general/colors-disabled.png index 19e9ad226..a966c1311 100644 Binary files a/packages/alphatab/test-data/visual-tests/general/colors-disabled.png and b/packages/alphatab/test-data/visual-tests/general/colors-disabled.png differ diff --git a/packages/alphatab/test-data/visual-tests/general/colors.png b/packages/alphatab/test-data/visual-tests/general/colors.png index 248f7247d..1f34d1b78 100644 Binary files a/packages/alphatab/test-data/visual-tests/general/colors.png and b/packages/alphatab/test-data/visual-tests/general/colors.png differ diff --git a/packages/alphatab/test-data/visual-tests/general/font-fallback.png b/packages/alphatab/test-data/visual-tests/general/font-fallback.png index 397d799d5..a1222646c 100644 Binary files a/packages/alphatab/test-data/visual-tests/general/font-fallback.png and b/packages/alphatab/test-data/visual-tests/general/font-fallback.png differ diff --git a/packages/alphatab/test-data/visual-tests/general/tuning.png b/packages/alphatab/test-data/visual-tests/general/tuning.png index aff4f080a..c9645a880 100644 Binary files a/packages/alphatab/test-data/visual-tests/general/tuning.png and b/packages/alphatab/test-data/visual-tests/general/tuning.png differ diff --git a/packages/alphatab/test-data/visual-tests/grace-spacing/grace-at-bar-boundary.png b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-at-bar-boundary.png new file mode 100644 index 000000000..c305c3e0f Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-at-bar-boundary.png differ diff --git a/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multi-track.png b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multi-track.png new file mode 100644 index 000000000..86c355064 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multi-track.png differ diff --git a/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multi-voice.png b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multi-voice.png new file mode 100644 index 000000000..9df7e8105 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multi-voice.png differ diff --git a/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multiple-consecutive.png b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multiple-consecutive.png new file mode 100644 index 000000000..256e443d3 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-multiple-consecutive.png differ diff --git a/packages/alphatab/test-data/visual-tests/grace-spacing/grace-single-before-regular-note.png b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-single-before-regular-note.png new file mode 100644 index 000000000..468fb6da8 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-single-before-regular-note.png differ diff --git a/packages/alphatab/test-data/visual-tests/grace-spacing/grace-without-host-incomplete-group.png b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-without-host-incomplete-group.png new file mode 100644 index 000000000..25961cb76 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/grace-spacing/grace-without-host-incomplete-group.png differ diff --git a/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png b/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png index d00d9e033..aa43588e7 100644 Binary files a/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png and b/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-with-beams.png differ diff --git a/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm.png b/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm.png index 0431f90f4..4dcebfc40 100644 Binary files a/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm.png and b/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm.png differ diff --git a/packages/alphatab/test-data/visual-tests/guitar-tabs/string-variations.png b/packages/alphatab/test-data/visual-tests/guitar-tabs/string-variations.png index 0861804be..6d652bfa1 100644 Binary files a/packages/alphatab/test-data/visual-tests/guitar-tabs/string-variations.png and b/packages/alphatab/test-data/visual-tests/guitar-tabs/string-variations.png differ diff --git a/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-above-threshold-stretched.png b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-above-threshold-stretched.png new file mode 100644 index 000000000..ced2acd7d Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-above-threshold-stretched.png differ diff --git a/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-below-threshold-not-stretched.png b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-below-threshold-not-stretched.png new file mode 100644 index 000000000..99590c939 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-below-threshold-not-stretched.png differ diff --git a/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-default-never-stretches.png b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-default-never-stretches.png new file mode 100644 index 000000000..99590c939 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-default-never-stretches.png differ diff --git a/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-threshold-disabled-stretches.png b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-threshold-disabled-stretches.png new file mode 100644 index 000000000..4a711766b Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/last-system-threshold/last-system-threshold-disabled-stretches.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-all.png b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-all.png index 79d27cb06..f02b1832c 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-all.png and b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-all.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-first.png b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-first.png index 85819c041..36f384855 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-first.png and b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-first.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-hide.png b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-hide.png index 4122aa8c1..4563b37dd 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-hide.png and b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-bar-override-hide.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-all.png b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-all.png index 7d764a325..e31e3c23e 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-all.png and b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-all.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-first.png b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-first.png index 3b2cf1554..86a6cb093 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-first.png and b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-first.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-hide.png b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-hide.png index be4f06aa6..5e72b5c1c 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-hide.png and b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-hide.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves-in-first.png b/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves-in-first.png index ff17ffc0f..a3bae5a89 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves-in-first.png and b/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves-in-first.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves.png b/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves.png index 03020a1ed..b8d60469c 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves.png and b/packages/alphatab/test-data/visual-tests/layout/hide-empty-staves.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/horizontal-layout-5to8.png b/packages/alphatab/test-data/visual-tests/layout/horizontal-layout-5to8.png index 64d6528c2..e65ee6053 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/horizontal-layout-5to8.png and b/packages/alphatab/test-data/visual-tests/layout/horizontal-layout-5to8.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/horizontal-layout.png b/packages/alphatab/test-data/visual-tests/layout/horizontal-layout.png index c0e5e337c..17853064d 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/horizontal-layout.png and b/packages/alphatab/test-data/visual-tests/layout/horizontal-layout.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-0-1300.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-0-1300.png index 0b8712152..997faab6a 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-0-1300.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-0-1300.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-1-600.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-1-600.png index aafe16457..4907bf69c 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-1-600.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-1-600.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-2-300.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-2-300.png index ba8acf74f..340aa3827 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-2-300.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-2-300.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-3-700.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-3-700.png index 169e4ecc7..3a09cec92 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-3-700.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-down-3-700.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-0-600.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-0-600.png index a3f77c935..251d77c37 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-0-600.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-0-600.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-1-1300.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-1-1300.png index b07c2fa24..c5557995a 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-1-1300.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-1-1300.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-2-700.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-2-700.png index 868480424..50f534654 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-2-700.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-2-700.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-3-300.png b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-3-300.png index ba8acf74f..340aa3827 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-3-300.png and b/packages/alphatab/test-data/visual-tests/layout/multi-system-slur-scale-up-3-300.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-track.png b/packages/alphatab/test-data/visual-tests/layout/multi-track.png index bf211092d..1ce61d3e8 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-track.png and b/packages/alphatab/test-data/visual-tests/layout/multi-track.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multi-voice.png b/packages/alphatab/test-data/visual-tests/layout/multi-voice.png index 2b2720443..95ca45757 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multi-voice.png and b/packages/alphatab/test-data/visual-tests/layout/multi-voice.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multibar-rest-all-tracks.png b/packages/alphatab/test-data/visual-tests/layout/multibar-rest-all-tracks.png index 1f43c818a..0fa867ce9 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multibar-rest-all-tracks.png and b/packages/alphatab/test-data/visual-tests/layout/multibar-rest-all-tracks.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multibar-rest-multi-track.png b/packages/alphatab/test-data/visual-tests/layout/multibar-rest-multi-track.png index ed5eae90c..d26df9d23 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multibar-rest-multi-track.png and b/packages/alphatab/test-data/visual-tests/layout/multibar-rest-multi-track.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/multibar-rest-single-track.png b/packages/alphatab/test-data/visual-tests/layout/multibar-rest-single-track.png index e593e2369..eeb8b837a 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/multibar-rest-single-track.png and b/packages/alphatab/test-data/visual-tests/layout/multibar-rest-single-track.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/page-layout-5barsperrow.png b/packages/alphatab/test-data/visual-tests/layout/page-layout-5barsperrow.png index aea34f916..f1958afa3 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/page-layout-5barsperrow.png and b/packages/alphatab/test-data/visual-tests/layout/page-layout-5barsperrow.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/page-layout-5to8.png b/packages/alphatab/test-data/visual-tests/layout/page-layout-5to8.png index 73bd1ca80..5bafe0ba6 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/page-layout-5to8.png and b/packages/alphatab/test-data/visual-tests/layout/page-layout-5to8.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/page-layout-justify-last-row.png b/packages/alphatab/test-data/visual-tests/layout/page-layout-justify-last-row.png index 4e6195adf..b1483ea8e 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/page-layout-justify-last-row.png and b/packages/alphatab/test-data/visual-tests/layout/page-layout-justify-last-row.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/page-layout.png b/packages/alphatab/test-data/visual-tests/layout/page-layout.png index b373a000c..254234278 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/page-layout.png and b/packages/alphatab/test-data/visual-tests/layout/page-layout.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-hide.png b/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-hide.png index 51e8108ac..78006bc98 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-hide.png and b/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-hide.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-show.png b/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-show.png index fe6c5715e..f5caf05a9 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-show.png and b/packages/alphatab/test-data/visual-tests/layout/single-staff-brackets-show.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/system-layout-tex.png b/packages/alphatab/test-data/visual-tests/layout/system-layout-tex.png index caf3d1112..79a9fa644 100644 Binary files a/packages/alphatab/test-data/visual-tests/layout/system-layout-tex.png and b/packages/alphatab/test-data/visual-tests/layout/system-layout-tex.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced-alphatex.png b/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced-alphatex.png index 26c7e2060..c1711e76d 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced-alphatex.png and b/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced-alphatex.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced.png b/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced.png index 86cf350f1..3a1066ed3 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced.png and b/packages/alphatab/test-data/visual-tests/music-notation/accidentals-advanced.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/accidentals.png b/packages/alphatab/test-data/visual-tests/music-notation/accidentals.png index eb037b196..c91dc3579 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/accidentals.png and b/packages/alphatab/test-data/visual-tests/music-notation/accidentals.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/beams-advanced.png b/packages/alphatab/test-data/visual-tests/music-notation/beams-advanced.png index d4b2c5a69..fb3178f98 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/beams-advanced.png and b/packages/alphatab/test-data/visual-tests/music-notation/beams-advanced.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/brushes-ukulele.png b/packages/alphatab/test-data/visual-tests/music-notation/brushes-ukulele.png index 788944975..f18ccf9ec 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/brushes-ukulele.png and b/packages/alphatab/test-data/visual-tests/music-notation/brushes-ukulele.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/forced-accidentals.png b/packages/alphatab/test-data/visual-tests/music-notation/forced-accidentals.png index ee577e793..70add6126 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/forced-accidentals.png and b/packages/alphatab/test-data/visual-tests/music-notation/forced-accidentals.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c3.png b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c3.png index 78728f37d..830c49749 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c3.png and b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c3.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c4.png b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c4.png index f90a8ba52..75771f12c 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c4.png and b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-c4.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-f4.png b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-f4.png index bba7c24eb..057719813 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-f4.png and b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-f4.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-g2.png b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-g2.png index e0a4f4335..d5f22f265 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-g2.png and b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures-g2.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures.png b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures.png index e0a4f4335..d5f22f265 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/key-signatures.png and b/packages/alphatab/test-data/visual-tests/music-notation/key-signatures.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/notes-rests-beams.png b/packages/alphatab/test-data/visual-tests/music-notation/notes-rests-beams.png index c4cc93483..e902cbd3e 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/notes-rests-beams.png and b/packages/alphatab/test-data/visual-tests/music-notation/notes-rests-beams.png differ diff --git a/packages/alphatab/test-data/visual-tests/music-notation/rest-collisions.png b/packages/alphatab/test-data/visual-tests/music-notation/rest-collisions.png index 9e2ecf4ad..77ce50964 100644 Binary files a/packages/alphatab/test-data/visual-tests/music-notation/rest-collisions.png and b/packages/alphatab/test-data/visual-tests/music-notation/rest-collisions.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/effects-off.png b/packages/alphatab/test-data/visual-tests/notation-elements/effects-off.png index b1f2056bd..eedd7128c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/effects-off.png and b/packages/alphatab/test-data/visual-tests/notation-elements/effects-off.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/effects-on.png b/packages/alphatab/test-data/visual-tests/notation-elements/effects-on.png index 7ea8810da..e394d87e0 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/effects-on.png and b/packages/alphatab/test-data/visual-tests/notation-elements/effects-on.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-off.png b/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-off.png index b8f0ca5b7..c232e57fb 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-off.png and b/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-off.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-on.png b/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-on.png index 1f5037778..bf1688eb6 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-on.png and b/packages/alphatab/test-data/visual-tests/notation-elements/guitar-tuning-on.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-off.png b/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-off.png index cc9dea729..dbe8c4746 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-off.png and b/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-off.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-on.png b/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-on.png index 7a207363e..6da242da3 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-on.png and b/packages/alphatab/test-data/visual-tests/notation-elements/parenthesis-on-tied-bends-on.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-album.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-album.png index 124b47821..c33e2251c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-album.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-album.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-all.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-all.png index 0913f7076..81d721a8d 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-all.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-all.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-artist.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-artist.png index 5d64706b9..63a6db751 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-artist.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-artist.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-copyright.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-copyright.png index ed2811063..a43058967 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-copyright.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-copyright.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-music.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-music.png index 1af0df6be..f1a41c498 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-music.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-music.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-subtitle.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-subtitle.png index b770d035a..2b65b0632 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-subtitle.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-subtitle.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-title.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-title.png index a74705346..84abc53ba 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-title.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-title.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words-and-music.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words-and-music.png index 36d7afc2f..06727d726 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words-and-music.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words-and-music.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words.png b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words.png index b7afc1c3f..2059f883d 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words.png and b/packages/alphatab/test-data/visual-tests/notation-elements/score-info-words.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-off.png b/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-off.png index 5f8af6dec..7082030bc 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-off.png and b/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-off.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-on.png b/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-on.png index fe2ed346b..9a97e5363 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-on.png and b/packages/alphatab/test-data/visual-tests/notation-elements/tab-notes-on-tied-bends-on.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/track-names-off.png b/packages/alphatab/test-data/visual-tests/notation-elements/track-names-off.png index ef4d6d984..6c15770fd 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/track-names-off.png and b/packages/alphatab/test-data/visual-tests/notation-elements/track-names-off.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/track-names-on.png b/packages/alphatab/test-data/visual-tests/notation-elements/track-names-on.png index 53cb474a9..08fea362a 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/track-names-on.png and b/packages/alphatab/test-data/visual-tests/notation-elements/track-names-on.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-off.png b/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-off.png index 894fbc5ba..6d5f64b41 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-off.png and b/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-off.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-on.png b/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-on.png index b3e5acf3b..324045fe4 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-on.png and b/packages/alphatab/test-data/visual-tests/notation-elements/zeros-on-dive-whammys-on.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-default.png index e2ce3dffb..76412ea8a 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-songbook.png index caa3434ef..4a5fa1d1e 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/accentuations-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-default.png index ebad91f0d..7ba213be1 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-songbook.png index ebad91f0d..7ba213be1 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/arpeggio-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/bends-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/bends-default.png index 15d1a628d..92a4d8734 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/bends-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/bends-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/bends-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/bends-songbook.png index 3b72eb225..bda19feae 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/bends-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/bends-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/chords-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/chords-default.png index b7c12d398..23dac2303 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/chords-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/chords-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/chords-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/chords-songbook.png index b7c12d398..23dac2303 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/chords-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/chords-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-default.png index 15bfb3ae7..43c0d8f60 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-songbook.png index 15bfb3ae7..43c0d8f60 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/crescendo-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/dead-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/dead-default.png index 8d6e11a02..e95dbc446 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/dead-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/dead-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/dead-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/dead-songbook.png index 8d6e11a02..e95dbc446 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/dead-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/dead-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-default.png index 4cec11582..d2cf129ce 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-songbook.png index 4cec11582..d2cf129ce 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/dynamics-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/fingering-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/fingering-default.png index 1fcf16d18..afdbedcc5 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/fingering-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/fingering-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/fingering-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/fingering-songbook.png index 5795b8636..505ba0853 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/fingering-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/fingering-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/full-default-large.png b/packages/alphatab/test-data/visual-tests/notation-legend/full-default-large.png index bbf9949be..7be28aa08 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/full-default-large.png and b/packages/alphatab/test-data/visual-tests/notation-legend/full-default-large.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/full-default-small.png b/packages/alphatab/test-data/visual-tests/notation-legend/full-default-small.png index 57faec369..1c5d184ab 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/full-default-small.png and b/packages/alphatab/test-data/visual-tests/notation-legend/full-default-small.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/full-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/full-default.png index 7c49b5266..e9ee3b3b6 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/full-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/full-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/full-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/full-songbook.png index 8ccb412b9..832459c23 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/full-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/full-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/grace-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/grace-default.png index e0d7c516e..0408e6c71 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/grace-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/grace-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/grace-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/grace-songbook.png index 43e6e3a02..bb74a063c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/grace-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/grace-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/hammer-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/hammer-default.png index 3e1c779b9..4b25bac56 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/hammer-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/hammer-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/hammer-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/hammer-songbook.png index 3e1c779b9..4b25bac56 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/hammer-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/hammer-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-default.png index c129660f8..c5d420a8c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-songbook.png index c129660f8..c5d420a8c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/harmonics-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-default.png index d5987aa41..e720c2221 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-songbook.png index 942e82a58..0b5d82239 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/let-ring-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/mixed-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/mixed-default.png index 09b1aa47d..df1d5bbb4 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/mixed-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/mixed-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/mixed-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/mixed-songbook.png index f836570bf..da9242827 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/mixed-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/mixed-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-default.png index 48d14d9eb..fae05d938 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-songbook.png index 60317e205..67a5fcd75 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/multi-grace-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-default.png index 7516a6217..b6510ddd8 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-songbook.png index 7516a6217..b6510ddd8 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/multi-voice-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-default.png index 22ff566de..f0f66f9f0 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-songbook.png index 22ff566de..f0f66f9f0 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/ottavia-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-default.png index 22c1fe9cf..117feab49 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-songbook.png index 22c1fe9cf..117feab49 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/pick-stroke-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1300.png b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1300.png index 5a3b437b2..f93e1fcc5 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1300.png and b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1300.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1500.png b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1500.png index 6114a47d1..ecb886a1d 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1500.png and b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-1500.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-500.png b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-500.png index d7cc33386..f6dfdfe49 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-500.png and b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-500.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-800.png b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-800.png index 7d6c122e6..60558238c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-800.png and b/packages/alphatab/test-data/visual-tests/notation-legend/resize-sequence-800.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/slash-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/slash-default.png index 55f1768cf..38f02696a 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/slash-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/slash-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/slash-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/slash-songbook.png index 55f1768cf..38f02696a 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/slash-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/slash-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/slides-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/slides-default.png index 8841ed27b..29bbecbaf 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/slides-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/slides-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/slides-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/slides-songbook.png index 8841ed27b..29bbecbaf 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/slides-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/slides-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/smufl-petaluma-1300.png b/packages/alphatab/test-data/visual-tests/notation-legend/smufl-petaluma-1300.png index ca949e06a..244dd329f 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/smufl-petaluma-1300.png and b/packages/alphatab/test-data/visual-tests/notation-legend/smufl-petaluma-1300.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-default.png index 90a1d2d11..fa0c2f263 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-songbook.png index 90a1d2d11..fa0c2f263 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/staccatissimo-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/sweep-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/sweep-default.png index f773d0015..9c93f56b9 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/sweep-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/sweep-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/sweep-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/sweep-songbook.png index f773d0015..9c93f56b9 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/sweep-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/sweep-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-default.png index c1381ef61..b80b59f92 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-songbook.png index c1381ef61..b80b59f92 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/tap-riff-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-default.png index d3b7e09f7..d37c38e83 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-songbook.png index 7dc88934c..04c61efe0 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/tempo-change-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/text-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/text-default.png index a7d44f6b3..3440e3cea 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/text-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/text-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/text-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/text-songbook.png index a7d44f6b3..3440e3cea 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/text-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/text-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-default.png index 8797e3235..44576927b 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-songbook.png index b646a0e7f..703e01d2f 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/tied-note-accidentals-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/trill-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/trill-default.png index 4e59d6b19..c3251f8c4 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/trill-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/trill-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/trill-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/trill-songbook.png index 4e59d6b19..c3251f8c4 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/trill-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/trill-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-default.png index 0ca246444..45de91083 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-songbook.png index 0ca246444..45de91083 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/triplet-feel-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-default.png index 32c6f8b0a..1dc90f70c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-songbook.png index 32c6f8b0a..1dc90f70c 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/vibrato-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/wah-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/wah-default.png index a4df43be3..16d71275b 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/wah-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/wah-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/wah-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/wah-songbook.png index a4df43be3..16d71275b 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/wah-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/wah-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/whammy-default.png b/packages/alphatab/test-data/visual-tests/notation-legend/whammy-default.png index 453fd7b73..3cf7d2e78 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/whammy-default.png and b/packages/alphatab/test-data/visual-tests/notation-legend/whammy-default.png differ diff --git a/packages/alphatab/test-data/visual-tests/notation-legend/whammy-songbook.png b/packages/alphatab/test-data/visual-tests/notation-legend/whammy-songbook.png index 18078017a..70f1b43e6 100644 Binary files a/packages/alphatab/test-data/visual-tests/notation-legend/whammy-songbook.png and b/packages/alphatab/test-data/visual-tests/notation-legend/whammy-songbook.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-beat-text-end-overflow.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-beat-text-end-overflow.png new file mode 100644 index 000000000..5d25351d7 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-beat-text-end-overflow.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-cross-bar-end-and-start.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-cross-bar-end-and-start.png new file mode 100644 index 000000000..fa4bed5f8 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-cross-bar-end-and-start.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-end-of-system.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-end-of-system.png new file mode 100644 index 000000000..74b75ef30 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-end-of-system.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-lyrics-tight-bar.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-lyrics-tight-bar.png new file mode 100644 index 000000000..bfdddb051 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-lyrics-tight-bar.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap-control.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap-control.png new file mode 100644 index 000000000..7b7e11f3f Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap-control.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap.png new file mode 100644 index 000000000..7156b5375 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-staff-aggregation.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-staff-aggregation.png new file mode 100644 index 000000000..3013dffdc Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-staff-aggregation.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-non-overlapping-lyrics-no-widening.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-non-overlapping-lyrics-no-widening.png new file mode 100644 index 000000000..b80d326e3 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-non-overlapping-lyrics-no-widening.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-reconciles-on-resize.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-reconciles-on-resize.png new file mode 100644 index 000000000..b83bde405 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-reconciles-on-resize.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-internal-overhang.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-internal-overhang.png new file mode 100644 index 000000000..ebd91e23e Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-internal-overhang.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-no-stretch.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-no-stretch.png new file mode 100644 index 000000000..24a2276fd Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-no-stretch.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-sparse-lyrics.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-sparse-lyrics.png new file mode 100644 index 000000000..f9cc6b040 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-sparse-lyrics.png differ diff --git a/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-text-plus-lyric-same-beat.png b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-text-plus-lyric-same-beat.png new file mode 100644 index 000000000..38c398016 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/overlay-rod-spacing/overlay-rod-text-plus-lyric-same-beat.png differ diff --git a/packages/alphatab/test-data/visual-tests/prefix-overhead/parchment-prefix-midline-keysig-change.png b/packages/alphatab/test-data/visual-tests/prefix-overhead/parchment-prefix-midline-keysig-change.png index 3c15332d7..cd916efef 100644 Binary files a/packages/alphatab/test-data/visual-tests/prefix-overhead/parchment-prefix-midline-keysig-change.png and b/packages/alphatab/test-data/visual-tests/prefix-overhead/parchment-prefix-midline-keysig-change.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/beaming-mode.png b/packages/alphatab/test-data/visual-tests/special-notes/beaming-mode.png index 6832b9f23..2244c0ae5 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/beaming-mode.png and b/packages/alphatab/test-data/visual-tests/special-notes/beaming-mode.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/dead-notes.png b/packages/alphatab/test-data/visual-tests/special-notes/dead-notes.png index 3b2f3221a..c7a4c8c4c 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/dead-notes.png and b/packages/alphatab/test-data/visual-tests/special-notes/dead-notes.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/ghost-notes.png b/packages/alphatab/test-data/visual-tests/special-notes/ghost-notes.png index 7a8b39669..911e0b5f4 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/ghost-notes.png and b/packages/alphatab/test-data/visual-tests/special-notes/ghost-notes.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300-2.png b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300-2.png index fa4355ab3..cf4ca4d38 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300-2.png and b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300-2.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300.png b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300.png index 4699573ff..d3a5dbb4b 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300.png and b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-1300.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-800.png b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-800.png index 19d4399ca..9d825d114 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-800.png and b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced-800.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced.png b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced.png index 4699573ff..d3a5dbb4b 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced.png and b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-advanced.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-alignment.png b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-alignment.png index 0a07b147e..8d9db5206 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-alignment.png and b/packages/alphatab/test-data/visual-tests/special-notes/grace-notes-alignment.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-notes/tied-notes.png b/packages/alphatab/test-data/visual-tests/special-notes/tied-notes.png index ad7d85254..0fd28ef72 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-notes/tied-notes.png and b/packages/alphatab/test-data/visual-tests/special-notes/tied-notes.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-tracks/grand-staff.png b/packages/alphatab/test-data/visual-tests/special-tracks/grand-staff.png index f202c8b3c..fdd7ec748 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-tracks/grand-staff.png and b/packages/alphatab/test-data/visual-tests/special-tracks/grand-staff.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-tracks/numbered-durations.png b/packages/alphatab/test-data/visual-tests/special-tracks/numbered-durations.png index 9b535c0e7..72e01c984 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-tracks/numbered-durations.png and b/packages/alphatab/test-data/visual-tests/special-tracks/numbered-durations.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-tracks/numbered-tuplets.png b/packages/alphatab/test-data/visual-tests/special-tracks/numbered-tuplets.png index 056c970d9..142cfe96d 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-tracks/numbered-tuplets.png and b/packages/alphatab/test-data/visual-tests/special-tracks/numbered-tuplets.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-tracks/numbered.png b/packages/alphatab/test-data/visual-tests/special-tracks/numbered.png index 554fbd63e..a0481411e 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-tracks/numbered.png and b/packages/alphatab/test-data/visual-tests/special-tracks/numbered.png differ diff --git a/packages/alphatab/test-data/visual-tests/special-tracks/slash.png b/packages/alphatab/test-data/visual-tests/special-tracks/slash.png index 7220afa89..296b98de9 100644 Binary files a/packages/alphatab/test-data/visual-tests/special-tracks/slash.png and b/packages/alphatab/test-data/visual-tests/special-tracks/slash.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/power-law-duration-proportions.png b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-duration-proportions.png new file mode 100644 index 000000000..feca2cacb Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-duration-proportions.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/power-law-rest-bar-not-ballooning.png b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-rest-bar-not-ballooning.png new file mode 100644 index 000000000..3168c4823 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-rest-bar-not-ballooning.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/power-law-spacing-ratio-loose.png b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-spacing-ratio-loose.png new file mode 100644 index 000000000..db574e280 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-spacing-ratio-loose.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/power-law-spacing-ratio-tight.png b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-spacing-ratio-tight.png new file mode 100644 index 000000000..6bc4e28c8 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-spacing-ratio-tight.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-high.png b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-high.png new file mode 100644 index 000000000..372543b5a Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-high.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-low.png b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-low.png new file mode 100644 index 000000000..dea97db58 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-low.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-aligns-same-duration-notes.png b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-aligns-same-duration-notes.png index b853426b0..1ca428335 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-aligns-same-duration-notes.png and b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-aligns-same-duration-notes.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-horizontal-preserves-local.png b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-horizontal-preserves-local.png index e781070d0..581900839 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-horizontal-preserves-local.png and b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-horizontal-preserves-local.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-multiple-short-arrivals.png b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-multiple-short-arrivals.png index e61c4f461..8d0216305 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-multiple-short-arrivals.png and b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-multiple-short-arrivals.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-page-automatic.png b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-page-automatic.png index 3159ca11a..8264b4f7b 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-page-automatic.png and b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-page-automatic.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-per-system-isolation.png b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-per-system-isolation.png index 92dbb7cf3..cb35fd3d1 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-per-system-isolation.png and b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-per-system-isolation.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-reconciles-on-resize.png b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-reconciles-on-resize.png index 496b23b7b..ae382dde5 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-reconciles-on-resize.png and b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-reconciles-on-resize.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-shorter-note-first.png b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-shorter-note-first.png index 7fc5a3594..f113a719b 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-shorter-note-first.png and b/packages/alphatab/test-data/visual-tests/system-spacing/shared-min-duration-shorter-note-first.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/stretch-formula-duration-spacing.png b/packages/alphatab/test-data/visual-tests/system-spacing/stretch-formula-duration-spacing.png index 109e71804..b87d238e0 100644 Binary files a/packages/alphatab/test-data/visual-tests/system-spacing/stretch-formula-duration-spacing.png and b/packages/alphatab/test-data/visual-tests/system-spacing/stretch-formula-duration-spacing.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/system-min-duration-grace-notes-excluded.png b/packages/alphatab/test-data/visual-tests/system-spacing/system-min-duration-grace-notes-excluded.png new file mode 100644 index 000000000..d5f6d96f3 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/system-min-duration-grace-notes-excluded.png differ diff --git a/packages/alphatab/test-data/visual-tests/system-spacing/system-min-duration-multi-track-aggregation.png b/packages/alphatab/test-data/visual-tests/system-spacing/system-min-duration-multi-track-aggregation.png new file mode 100644 index 000000000..d62f0544e Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/system-spacing/system-min-duration-multi-track-aggregation.png differ diff --git a/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-automatic.png b/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-automatic.png index b82cea073..7061c3545 100644 Binary files a/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-automatic.png and b/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-automatic.png differ diff --git a/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-model.png b/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-model.png index bdc5ca000..f50d06edb 100644 Binary files a/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-model.png and b/packages/alphatab/test-data/visual-tests/systems-layout/bars-adjusted-model.png differ diff --git a/packages/alphatab/test-data/visual-tests/systems-layout/multi-track-single-track.png b/packages/alphatab/test-data/visual-tests/systems-layout/multi-track-single-track.png index b2a837a48..737945763 100644 Binary files a/packages/alphatab/test-data/visual-tests/systems-layout/multi-track-single-track.png and b/packages/alphatab/test-data/visual-tests/systems-layout/multi-track-single-track.png differ diff --git a/packages/alphatab/test/rendering/BarLayoutingInfo.test.ts b/packages/alphatab/test/rendering/BarLayoutingInfo.test.ts new file mode 100644 index 000000000..b61e56f43 --- /dev/null +++ b/packages/alphatab/test/rendering/BarLayoutingInfo.test.ts @@ -0,0 +1,218 @@ +import { describe, expect, it } from 'vitest'; +import { BarLayoutingInfo } from '@coderline/alphatab/rendering/staves/BarLayoutingInfo'; + +/** + * Unit tests pinning the power-law spring formula introduced by the spacing spike. + * + * Section 6.3 of `docs/spacing/spacing-spike-plan.md` calls for math-only invariants on + * `phi(d, dmin, r) = (d / dmin) ^ log2(r)` to catch accidental formula changes without + * relying on visual diffs. + * + * `BarLayoutingInfo._calculateSpringConstant` is private; we exercise it through the public + * `addSpring` + `finish` flow and read `spring.springConstant` from the public `springs` map. + * + * Spring constant for the chosen rod is computed as + * + * k = (smallestDuration / duration) * (1 / (phi * minDurationWidth)) + * + * For a single-spring bar where `smallestDuration == duration`, this collapses to + * + * k = 1 / (phi * minDurationWidth) + * + * which is what we assert against - it is a direct read of `phi` modulo a fixed scale. + * + * Two-spring bars use the gap between the springs as the duration of the first spring (see + * `_calculateSpringConstants`). We use that pattern to pin specific (d, dmin) pairs. + */ +describe('BarLayoutingInfoPowerLawFormula', () => { + /** `_defaultMinDurationWidth` from BarLayoutingInfo - kept private; mirrored here. */ + const minDurationWidth = 6.5; + + /** + * Builds a single-spring `BarLayoutingInfo` whose only spring covers exactly `duration` ticks + * and forces the reference minimum-duration to `duration` via `recomputeSpringConstants`. With + * `d == dmin == duration`, phi collapses to `(d/dmin)^exponent = 1` independent of the ratio, + * so this helper is mainly used to confirm the `phi=1` baseline. + * + * Returns phi derived from the spring constant by inverting `k = 1 / (phi * minDurationWidth)`. + */ + function phiAtMinDuration(duration: number, spacingRatio: number): number { + const info = new BarLayoutingInfo(spacingRatio); + info.addSpring(0, duration, 0, 0, 0); + info.addSpring(duration, duration, 0, 0, 0); + info.finish(); + // Force the system-wide reference to `duration` (mimics StaffSystem._trackSystemMinDuration). + info.recomputeSpringConstants(duration); + const spring = info.springs.get(0)!; + return 1 / (spring.springConstant * minDurationWidth); + } + + /** + * Builds a `BarLayoutingInfo` such that the spring under test has duration `d` (gap to the + * next spring) and the system-wide minimum-duration reference is `dmin`. The reference is + * forced via `recomputeSpringConstants(dmin)` after `finish()`, mirroring the path + * `StaffSystem` takes when a bar's local minimum disagrees with the system minimum. + * + * Returns phi for the spring under test, derived from its spring constant. Note that the + * spring's `smallestDuration` equals its `duration` here (a single duration was added at that + * spring's time position), so the `(smallestDuration / duration)` factor in + * `_calculateSpringConstant` is 1 and the read of `phi` is uncontaminated. + */ + function phiFromGap(d: number, dmin: number, spacingRatio: number): number { + const info = new BarLayoutingInfo(spacingRatio); + // Spring 0 at t=0, gap to next = d -> spring 0 gets duration `d`. + info.addSpring(0, d, 0, 0, 0); + info.addSpring(d, d, 0, 0, 0); + info.finish(); + info.recomputeSpringConstants(dmin); + const spring = info.springs.get(0)!; + return 1 / (spring.springConstant * minDurationWidth); + } + + describe('phi values match Section 3.3 reference table', () => { + // Reference values from spike plan Section 3.3, r = 1.5 column: + // d/dmin = 1 -> phi = 1.00 + // d/dmin = 2 -> phi = 1.50 + // d/dmin = 4 -> phi = 2.25 + // d/dmin = 8 -> phi = 3.38 (3.375 exact) + // d/dmin = 16 -> phi = 5.06 (5.0625 exact) + const r = 1.5; + + it('phi(d/dmin=1) = 1.0', () => { + expect(phiAtMinDuration(60, r)).toBeCloseTo(1.0, 6); + }); + + it('phi(d/dmin=2) = 1.5', () => { + expect(phiFromGap(120, 60, r)).toBeCloseTo(1.5, 6); + }); + + it('phi(d/dmin=4) = 2.25', () => { + expect(phiFromGap(240, 60, r)).toBeCloseTo(2.25, 6); + }); + + it('phi(d/dmin=8) = 3.375', () => { + expect(phiFromGap(480, 60, r)).toBeCloseTo(3.375, 6); + }); + + it('phi(d/dmin=16) = 5.0625', () => { + expect(phiFromGap(960, 60, r)).toBeCloseTo(5.0625, 6); + }); + }); + + describe('phi values for r = sqrt(2) (Dorico default)', () => { + // From plan Section 3.3, r = 1.414 column: + // d/dmin = 2 -> phi = sqrt(2) ≈ 1.41421 + // d/dmin = 4 -> phi = 2.0 + // d/dmin = 8 -> phi = 2*sqrt(2) ≈ 2.82843 + // d/dmin = 16 -> phi = 4.0 + const r = Math.SQRT2; + + it('phi(d/dmin=2) = sqrt(2)', () => { + expect(phiFromGap(120, 60, r)).toBeCloseTo(Math.SQRT2, 6); + }); + + it('phi(d/dmin=4) = 2.0', () => { + expect(phiFromGap(240, 60, r)).toBeCloseTo(2.0, 6); + }); + + it('phi(d/dmin=16) = 4.0', () => { + expect(phiFromGap(960, 60, r)).toBeCloseTo(4.0, 6); + }); + }); + + describe('phi values for r = phi (Finale Fibonacci)', () => { + // From plan Section 3.3, r = 1.618 column: + // d/dmin = 2 -> phi = 1.618... + // d/dmin = 4 -> phi ≈ 2.618 + // d/dmin = 8 -> phi ≈ 4.236 + const r = 1.618; + + it('phi(d/dmin=2) = 1.618', () => { + expect(phiFromGap(120, 60, r)).toBeCloseTo(1.618, 3); + }); + + it('phi(d/dmin=4) = 2.618', () => { + // 1.618^2 = 2.617924 + expect(phiFromGap(240, 60, r)).toBeCloseTo(1.618 * 1.618, 4); + }); + }); + + it('spring constants decrease monotonically as duration grows', () => { + // For a fixed dmin, longer durations should produce smaller spring constants + // (= softer springs that take more space under the same justification force). + const r = 1.5; + const k60 = 1 / (phiAtMinDuration(60, r) * minDurationWidth); + const k120 = 1 / (phiFromGap(120, 60, r) * minDurationWidth); + const k240 = 1 / (phiFromGap(240, 60, r) * minDurationWidth); + const k480 = 1 / (phiFromGap(480, 60, r) * minDurationWidth); + const k960 = 1 / (phiFromGap(960, 60, r) * minDurationWidth); + + expect(k60).toBeGreaterThan(k120); + expect(k120).toBeGreaterThan(k240); + expect(k240).toBeGreaterThan(k480); + expect(k480).toBeGreaterThan(k960); + }); + + it('rest-bar spring constant is no longer collapsed against a dense neighbor (smoke check)', () => { + // Re-creates the headline rest-bar-ballooning scenario in numbers: a sixteenth-note bar + // and a whole-rest bar should produce spring constants in a ratio close to spacingRatio^4 + // (whole / sixteenth = 16x, log2(16) = 4 doublings). Under the OLD additive formula + // `phi = 1 + 0.85 * log2(d/dmin)`, the whole-rest bar got phi = 1.0 (because d == dmin in + // its own bar) and the sixteenth bar got phi = 1.0 too - making them equally stiff and + // the rest bar absorb justification force disproportionately. With system-wide _minDuration + // (A2) and the new power-law formula (A1), the whole rest now sits at phi = r^4 against a + // 60-tick reference. + const r = 1.5; + const phi16 = phiAtMinDuration(60, r); // d/dmin = 1 + const phiWhole = phiFromGap(960, 60, r); // d/dmin = 16 + // Whole rest is `r^4 = 1.5^4 = 5.0625` times stiffer in *natural length*, which is the + // correct, proportional behaviour. The previous formula gave 1.0 for both - the bug. + expect(phiWhole / phi16).toBeCloseTo(Math.pow(r, 4), 4); + }); + + it('r = 1.0 produces equal phi for all durations (degenerate baseline)', () => { + // Degenerate case: r=1 means `exponent = log2(1) = 0`, so phi = 1 for every duration. + // The clamp pulls r=1.0 up to 1.2 (the documented minimum), so this test verifies the + // *clamped* behavior - phi for r=1.0 should equal phi for r=1.2. + const phiAt1 = phiFromGap(240, 60, 1.0); + const phiAt12 = phiFromGap(240, 60, 1.2); + expect(phiAt1).toBeCloseTo(phiAt12, 6); + }); + + it('sub-minimum duration produces phi < 1 (compression)', () => { + // d/dmin < 1 means the spring is shorter than the system minimum reference. phi should + // drop below 1 so that the spring is *stiffer* than the reference (less stretchy). + // This case can arise during reconcile when an earlier bar's duration is shorter than + // the reference passed in by the system. + const info = new BarLayoutingInfo(1.5); + info.addSpring(0, 60, 0, 0, 0); + info.addSpring(60, 60, 0, 0, 0); + // After finish(), recompute against a larger minDuration than this bar's local one. + info.finish(); + info.recomputeSpringConstants(120); // pretend the system min is 120 (eighth) + const spring = info.springs.get(0)!; + // Spring duration = gap to next spring = 60. With minDuration = 120, d/dmin = 0.5. + // phi = 0.5 ^ log2(1.5) = 0.5 ^ 0.5849... ≈ 0.6667 + const phi = 1 / (spring.springConstant * minDurationWidth); + expect(phi).toBeLessThan(1.0); + expect(phi).toBeCloseTo(Math.pow(0.5, Math.log2(1.5)), 6); + }); + + describe('spacingExponentFromRatio clamps to documented range', () => { + it('returns log2(r) for r in range', () => { + expect(BarLayoutingInfo.spacingExponentFromRatio(1.5)).toBeCloseTo(Math.log2(1.5), 10); + expect(BarLayoutingInfo.spacingExponentFromRatio(Math.SQRT2)).toBeCloseTo(0.5, 10); + expect(BarLayoutingInfo.spacingExponentFromRatio(2.0)).toBeCloseTo(1.0, 10); + }); + + it('clamps r < 1.2 up to 1.2', () => { + expect(BarLayoutingInfo.spacingExponentFromRatio(1.0)).toBeCloseTo(Math.log2(1.2), 10); + expect(BarLayoutingInfo.spacingExponentFromRatio(0.5)).toBeCloseTo(Math.log2(1.2), 10); + }); + + it('clamps r > 2.0 down to 2.0', () => { + expect(BarLayoutingInfo.spacingExponentFromRatio(2.5)).toBeCloseTo(1.0, 10); + expect(BarLayoutingInfo.spacingExponentFromRatio(10)).toBeCloseTo(1.0, 10); + }); + }); +}); diff --git a/packages/alphatab/test/visualTests/features/GraceNoteSpacing.test.ts b/packages/alphatab/test/visualTests/features/GraceNoteSpacing.test.ts new file mode 100644 index 000000000..13157a3af --- /dev/null +++ b/packages/alphatab/test/visualTests/features/GraceNoteSpacing.test.ts @@ -0,0 +1,170 @@ +import { describe, it } from 'vitest'; +import { LayoutMode } from '@coderline/alphatab/LayoutMode'; +import { Settings } from '@coderline/alphatab/Settings'; +import { VisualTestHelper } from 'test/visualTests/VisualTestHelper'; + +/** + * Phase 3 (A3) of the spacing spike: targeted visual coverage for the six grace note scenarios + * called out in `docs/spacing/spacing-spike-plan.md` Section 2.2 (A3) and 6.3. + * + * AlphaTab follows Dorico's Model 3 for grace notes: graces are placed before their host note + * column, their internal layout uses fixed pre-beat + post-spring widths per grace, and they do + * not participate in the main spring system - their total width is folded into the host beat's + * `graceBeatWidth` and consumed as a pre-beat rod extension on the host's spring. + * + * The power-law formula introduced by A1 does not touch grace springs at all (graces go through + * `addBeatSpring`'s grace branch, never `addSpring`). These tests pin behaviour-level invariants + * that should remain true regardless of the formula: + * + * 1. Single grace before a regular note + * 2. Multiple consecutive graces in one group + * 3. Grace at a bar boundary (graces belonging to the first beat of the next bar) + * 4. Grace with no following host (incomplete group) + * 5. Grace in a multi-voice context + * 6. Grace across staves in a multi-track layout + * + * Existing coverage in `SpecialNotes.test.ts` (`grace-notes`, `grace-notes-advanced`, + * `grace-notes-alignment`) is GP-fixture-driven and broad. The tests here are alphaTex-driven + * and minimal so that one specific invariant per test is exposed. + */ +describe('GraceNoteSpacingTests', () => { + it('grace-single-before-regular-note', async () => { + // Simplest case: one before-beat grace eighth before the first quarter of bar 1, plain + // quarters thereafter. The grace cluster sits left of the host quarter without disturbing + // the spring positions of the rest of the bar. + // + // Expected baseline: a single small grace notehead with flag, anchored just before the + // first regular quarter at bar start. The remaining three quarters are evenly spaced. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :8 c5 {gr bb} :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/grace-spacing/grace-single-before-regular-note.png', + settings + ); + }); + + it('grace-multiple-consecutive', async () => { + // Three before-beat grace 32nds attached to bar 1's first quarter. The grace group's + // internal spacing uses fixed widths per grace (current alphaTab behaviour); under the + // power-law formula change the regular-note spring positions to the right of the host + // are unaffected because graces do not contribute to `_minDuration` and have their own + // pre-beat rod width separate from the spring system. + // + // Expected baseline: three small grace noteheads beamed together as a cluster, anchored + // before the first regular quarter. Quarters 2/3/4 evenly spaced. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :32 c5 {gr bb} d5 {gr bb} e5 {gr bb} :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/grace-spacing/grace-multiple-consecutive.png', + settings + ); + }); + + it('grace-at-bar-boundary', async () => { + // Bar 1 ends with a before-beat grace 32nd that belongs to bar 2's first quarter (the + // graceGroup's host is in the next bar). The grace must render visually at the end of + // bar 1 (before the barline) but the host pointer keeps the spring system in bar 2 + // anchored on bar 2's first quarter. + // + // Expected baseline: bar 1 has four quarters followed by a small grace cluster just + // before the barline; bar 2 starts with the host quarter that the grace anchors to. + // The barline sits between the grace and the host. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 2 } + \\ts 4 4 + :4 c4 c4 c4 c4 :32 c5 {gr bb} | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/grace-spacing/grace-at-bar-boundary.png', + settings + ); + }); + + it('grace-without-host-incomplete-group', async () => { + // Bar 1 ends with a before-beat grace, and there is no following beat anywhere in the + // score (no bar 2). This is the "incomplete grace group" path - common in imported + // Guitar Pro files with invalid notation. The renderer must place the grace at a + // reasonable position (within bar 1's available space) and not distort the system + // width via `_incompleteGraceRodsWidth` under the new formula. + // + // Expected baseline: a single bar with four quarters followed by a small grace cluster + // before the closing barline. No layout corruption, no negative widths, no missing + // glyphs. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :4 c4 c4 c4 c4 :32 c5 {gr bb} + `, + 'test-data/visual-tests/grace-spacing/grace-without-host-incomplete-group.png', + settings + ); + }); + + it('grace-multi-voice', async () => { + // Two voices in a single bar. Voice 1 has a before-beat grace eighth before its first + // quarter; voice 2 has plain quarters. The grace in voice 1 must not shift the time-axis + // alignment of voice 2's quarters - they share the same spring system, and graces + // contribute only to the pre-beat rod width of the host column they anchor to. + // + // Expected baseline: two stacked quarter-note voices with their notes aligned column-by- + // column. A small grace notehead sits before voice 1's first quarter without pushing + // voice 2's first quarter off the column. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + \\voice + :8 c5 {gr bb} :4 c5 c5 c5 c5 + \\voice + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/grace-spacing/grace-multi-voice.png', + settings + ); + }); + + it('grace-multi-track', async () => { + // Two tracks with one bar each. Track 1 has plain quarters; Track 2 starts with a + // before-beat grace eighth before its first quarter. The grace cluster in Track 2 must + // not displace the column alignment between Track 1 and Track 2 - a host beat in either + // track at the same time position must still render at the same x-coordinate. + // + // Expected baseline: two stacked staves; quarter notes align column-by-column across + // tracks; Track 2's first quarter has a small grace notehead anchored to its left. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Page; + await VisualTestHelper.runVisualTestTex( + ` + \\track "T1" + \\ts 4 4 + :4 c4 c4 c4 c4 + \\track "T2" + \\ts 4 4 + :8 c5 {gr bb} :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/grace-spacing/grace-multi-track.png', + settings, + o => { + o.tracks = [0, 1]; + } + ); + }); +}); diff --git a/packages/alphatab/test/visualTests/features/LastSystemThreshold.test.ts b/packages/alphatab/test/visualTests/features/LastSystemThreshold.test.ts new file mode 100644 index 000000000..194e1053d --- /dev/null +++ b/packages/alphatab/test/visualTests/features/LastSystemThreshold.test.ts @@ -0,0 +1,164 @@ +import { describe, it } from 'vitest'; +import { LayoutMode } from '@coderline/alphatab/LayoutMode'; +import { Settings } from '@coderline/alphatab/Settings'; +import { VisualTestHelper } from 'test/visualTests/VisualTestHelper'; + +/** + * Phase 4 (B1) of the spacing spike: visual coverage for the last-system fill threshold. + * + * Industry convention (Dorico, MuseScore): when the final system of a flow is sparsely + * populated relative to the available staff width, it looks better rendered at its natural + * width than stretched to fill the row. The {@link DisplaySettings.lastSystemFillThreshold} + * setting decides where that boundary sits. + * + * Semantics: + * - `lastSystemFillThreshold = 1` (default) — never justify the last system. + * - `lastSystemFillThreshold = 0` — always justify, even when sparse. + * - `lastSystemFillThreshold = 0.4`–`0.7` — Dorico/MuseScore-style: justify only when the + * last system is reasonably full. + * + * The legacy {@link DisplaySettings.justifyLastSystem} property is now a deprecated + * computed accessor on top of the threshold (`true` ⇔ threshold `< 1`). + */ +describe('LastSystemThresholdTests', () => { + it('last-system-below-threshold-not-stretched', async () => { + // 12 bars laid out in a page layout where the line break splits 11 + 1. The lone + // final bar's natural width is only ~7-10% of the available staff width. With an + // intermediate threshold of 0.5, the lone bar's fullness is below the threshold so + // justification is suppressed and the final bar renders at its natural compact width. + // + // Expected baseline: row 1 has 11 bars filling the system; row 2 has a single short + // bar at the left (not stretched to the right edge). + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Page; + settings.display.lastSystemFillThreshold = 0.5; + settings.display.barsPerRow = 11; + await VisualTestHelper.runVisualTestTex( + ` + \\ts 4 4 + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/last-system-threshold/last-system-below-threshold-not-stretched.png', + settings + ); + }); + + it('last-system-above-threshold-stretched', async () => { + // 19 bars in Parchment mode with explicit systemsLayout 11+8 directing the wrap. Row 1 + // holds 11 bars (densely filling); row 2 holds 8 bars whose natural width is ~73% of + // row 1. With `lastSystemFillThreshold = 0.5`, row 2 is above the threshold so it IS + // justified to fill the available staff width. + // + // Expected baseline: two rows, both filling the full staff width. The 8 bars in row 2 + // are visibly wider per bar than the 11 bars in row 1 because the same staff width is + // distributed across fewer bars. The right edge of both rows aligns with the page + // padding - confirming the last system was justified, not left at its narrower + // natural width. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + settings.display.lastSystemFillThreshold = 0.5; + await VisualTestHelper.runVisualTestTex( + ` + \\track { systemsLayout 11 8 } + \\ts 4 4 + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/last-system-threshold/last-system-above-threshold-stretched.png', + settings + ); + }); + + it('last-system-threshold-disabled-stretches', async () => { + // Same score as `last-system-below-threshold-not-stretched` but with the threshold + // explicitly set to `0` (always justify). The lone final bar stretches to fill the + // full row regardless of fullness. Equivalent to the legacy + // `justifyLastSystem = true` behaviour. Confirms the always-justify path. + // + // Expected baseline: row 1 has 11 bars; row 2 has the single bar stretched across + // the full available width. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Page; + settings.display.lastSystemFillThreshold = 0; + settings.display.barsPerRow = 11; + await VisualTestHelper.runVisualTestTex( + ` + \\ts 4 4 + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/last-system-threshold/last-system-threshold-disabled-stretches.png', + settings + ); + }); + + it('last-system-default-never-stretches', async () => { + // Same score as `last-system-threshold-disabled-stretches` but at the default + // `lastSystemFillThreshold = 1` (never justify). The lone final bar stays compact + // regardless of fullness, matching the legacy `justifyLastSystem = false` default. + // + // Expected baseline: row 1 has 11 bars; row 2 has the single bar at its natural + // compact width at the left of the row. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Page; + // lastSystemFillThreshold left at default 1 + settings.display.barsPerRow = 11; + await VisualTestHelper.runVisualTestTex( + ` + \\ts 4 4 + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/last-system-threshold/last-system-default-never-stretches.png', + settings + ); + }); +}); diff --git a/packages/alphatab/test/visualTests/features/OverlayRodSpacing.test.ts b/packages/alphatab/test/visualTests/features/OverlayRodSpacing.test.ts new file mode 100644 index 000000000..3e8402594 --- /dev/null +++ b/packages/alphatab/test/visualTests/features/OverlayRodSpacing.test.ts @@ -0,0 +1,359 @@ +import { LayoutMode } from '@coderline/alphatab/LayoutMode'; +import { Settings } from '@coderline/alphatab/Settings'; +import { StaveProfile } from '@coderline/alphatab/StaveProfile'; +import { VisualTestHelper } from 'test/visualTests/VisualTestHelper'; +import { describe, it } from 'vitest'; + +/** + * Behaviour-focused coverage for the **overlay rod system** introduced as Topic 8 of the + * spacing spike (see `docs/spacing/spacing-followup-topics.md`, section 8). + * + * Overlay rods are a first-class minimum-distance constraint registered against beats + * that carry overlay content (lyrics, beat text). They live on `BarLayoutingInfo` + * parallel to the rhythmic spring-rod model and contribute additional `minStretchForce` + * constraints inside the existing `_calculateSpringConstants` flow. Beats without + * overlays never register a rod, so: + * + * - **non-overlapping content does not bloat spacing** — short syllables that fit + * between adjacent beats produce the same width as a no-lyrics control bar. + * - **sparse lyrics work naturally** — the overlay-rod loop walks only registered + * rods, regardless of how many silent (non-overlay) beats sit between two overlay- + * bearing beats. Multiple springs in between contribute through the `sum(1/k)` + * term of the multi-spring force constraint. + * + * Each test below pins one specific invariant of this system. Together they form the + * regression guard for the overlay-rod calculation in + * `BarLayoutingInfo._calculateSpringConstants` and the registration code in + * `BarRendererBase._registerOverlayRods` / `_collectOverlayRods`. + */ +describe('OverlayRodSpacingTests', () => { + /** + * Long syllables on every quarter would overlap at default spacing. The overlay-rod + * constraint forces the bar to widen so no syllable touches its neighbour. Baseline: + * the bar is visibly wider than a no-lyrics control bar (test 3) and the four + * syllables remain legible without overlap. + */ + it('overlay-rod-lyrics-tight-bar', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics "happiness birthday celebration congratulations" + \\ts 4 4 + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-lyrics-tight-bar.png', + settings + ); + }); + + /** + * Lyrics only on beats 1 and 3 of a 4-beat bar; beats 2 and 4 are silent. The + * overlay-rod loop must walk by registered overlay-bearing beats (1 -> 3), spanning + * two springs (1->2 and 2->3) via the `sum(1/k)` multi-spring force constraint. + * Beats 2 and 4 stay close to their natural spring positions because no rod + * inflation reaches them. Baseline: gap between beats 1 and 3 visibly widened to + * clear the long syllables, beats 2 / 4 are not bloated. + */ + it('overlay-rod-sparse-lyrics', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics "happiness congratulations " + \\ts 4 4 + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-sparse-lyrics.png', + settings + ); + }); + + /** + * Short syllables that fit between adjacent beats at default spacing. No overlap + * means no overlay-rod force contribution, so the bar's width is identical to the + * no-lyrics control bar. This pins the "no bloat for non-overlapping content" + * principle — the overlay-rod system must NEVER inflate spacing just because lyrics + * exist. + */ + it('overlay-rod-non-overlapping-lyrics-no-widening', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics "a b c d" + \\ts 4 4 + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-non-overlapping-lyrics-no-widening.png', + settings + ); + }); + + /** + * Right-edge overflow via the phantom-next-beat reframe. A long left-aligned + * beat-text on the LAST beat of a bar has rightExtent = full text width, so the + * overlay extends past the natural rhythmic post-content of the last beat. The + * last spring's `requiredSpace` becomes max(postSpringWidth, rightExtent + padding), + * force grows accordingly, and the bar widens such that the text ends inside the + * voice container before the barline. Pins right-edge overflow handling + * exclusively — does NOT exercise pair-overlap (only one rod registered). + */ + it('overlay-rod-beat-text-end-overflow', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :4 c4 c4 c4 c4 {txt "ALongBeatTextAnnotation"} + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-beat-text-end-overflow.png', + settings + ); + }); + + /** + * Multi-staff control: the same long lyric on a guitar tab + score combination must + * widen both staves identically because they share the master bar's + * BarLayoutingInfo and `addOverlayRod` max-merges duplicate registrations from each + * staff. Baseline: the tab and score staves align column-by-column even though the + * tab numbers are narrower than the score noteheads. + */ + it('overlay-rod-multi-staff-aggregation', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + settings.display.staveProfile = StaveProfile.ScoreTab; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics "happiness birthday celebration congratulations" + \\ts 4 4 + :4 3.3.4 3.3.4 3.3.4 3.3.4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-staff-aggregation.png', + settings + ); + }); + + /** + * Single overlay rod that fits naturally inside the bar — should NOT trigger any + * widening. This pins the no-bloat invariant for the isolated-rod case (no pair + * neighbour to overlap with, and the rod's extent is much smaller than the bar's + * natural rhythmic content). Baseline: the bar's width must match a no-overlay + * control rendering of the same rhythm. + */ + it('overlay-rod-single-rod-no-stretch', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics " hi " + \\ts 4 4 + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-no-stretch.png', + settings + ); + }); + + /** + * SINGLE ROD INTERNAL: a long left-aligned beat text on the FIRST beat of a 4-beat + * bar. Only one overlay rod exists in the bar, anchored on a non-last beat. No + * pair-overlap can fire (no neighbour rod). The last-rod handler does not fire + * (the rod is not on the last spring). Result: no force adjustment, the text + * overhangs freely over subsequent beats inside the bar. This pins the MVP + * behavior for the "single rod, internal, overhangs" scenario as a known gap. + * + * TODO(overlay-rods, internal-overhang): a future refinement could detect when a + * single rod's rightExtent crosses subsequent beats and widen the rod's host + * spring accordingly — symmetric to the last-rod phantom mechanism but against + * the rod's actual next spring rather than the bar edge. + */ + it('overlay-rod-single-rod-internal-overhang', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :4 c4 {txt "ALongBeatTextAnnotation"} c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-single-rod-internal-overhang.png', + settings + ); + }); + + /** + * Multi-bar score with lyrics on the LAST beat of bar 1 and FIRST beat of bar 2. + * The two rods sit in different `BarLayoutingInfo` instances, so the in-bar + * pair-overlap cannot see them as a pair. Bar 1's last-rod overflow handling + * widens its trailing space (rightExtent past `postSpringWidth + postBeatSize`); + * bar 2's leading lyric is currently NOT handled (left-edge gap is documented as + * an MVP gap). Baseline pins how cross-bar lyrics render at MVP — typically clear + * on bar 1's side, may touch the barline on bar 2's side. Doc-block IS the spec. + */ + it('overlay-rod-cross-bar-end-and-start', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 2 } + \\lyrics "a b c celebration congratulations b c d" + \\ts 4 4 + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-cross-bar-end-and-start.png', + settings + ); + }); + + /** + * RESIZE / RECOMPUTE: pair-overlap constraint must be re-applied when the system + * is resized. After the initial layout produces a `minStretchForce` that includes + * the overlay constraint, a width change triggers `reconcileMinDurationIfDirty` + * which calls `recomputeSpringConstants` -> `_calculateSpringConstants`. The + * overlay-rod loop is INSIDE `_calculateSpringConstants`, so the constraint is + * re-applied automatically. Guards regression where the constraint might be + * cached / orphaned and only applied at first layout. Renders a 2-bar score with + * overlapping lyrics at a narrower width than the natural fit. + */ + it('overlay-rod-reconciles-on-resize', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 2 } + \\lyrics "happiness birthday celebration congratulations happiness birthday celebration congratulations" + \\ts 4 4 + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + '', + settings, + o => { + o.runs[0].width = 900; + o.runs[0].referenceFileName = + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-reconciles-on-resize.png'; + } + ); + }); + + /** + * END-OF-SYSTEM: the LAST bar of a system carrying overlay rods on multiple beats. + * Right-edge overflow on the last beat of the last bar of a system is the + * end-of-system case. The phantom-next-beat handler still fires because each bar + * sees the same right-edge logic regardless of whether it is the last bar in the + * system. Pin the rendering so future system-level cross-bar accumulator work has + * a fixed reference for the MVP behavior. Uses Page mode so the system is fully + * justified, exercising the interaction between overlay constraints and system + * justification. + */ + it('overlay-rod-end-of-system', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Page; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 3 } + \\lyrics "happiness birthday celebration happiness birthday celebration happiness birthday celebration" + \\ts 4 4 + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-end-of-system.png', + settings + ); + }); + + /** + * Beat text AND lyric on the SAME beat (beat 2). Rods in different bands are + * kept in SEPARATE buckets — lyrics live in the `EffectLyrics` bucket and beat + * text lives in the `EffectText` bucket. They do NOT merge into one rod, and + * pair-overlap runs independently per band. + * + * Lyric bucket: rods on beats 1 (`x`), 2 (`widewidewideword`), 3 (`x`), 4 (`x`). + * The wide lyric on beat 2 pair-overlaps with the narrow lyrics on beats 1 and + * 3, which drives the visible widening of the bar. + * + * Text bucket: a single rod on beat 2 (`shortTxt`). No pair partner exists in + * this bucket, so the text contributes NO pair-overlap force. The previous + * single-stream design erroneously max-merged the text width into the lyric's + * rod on beat 2; with bucketing that no longer happens. + */ + it('overlay-rod-text-plus-lyric-same-beat', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics "x widewidewideword x x" + \\ts 4 4 + :4 c4 c4 {txt "shortTxt"} c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-text-plus-lyric-same-beat.png', + settings + ); + }); + + /** + * MULTI-BAND BUCKETING REGRESSION GUARD — control case. + * + * Wide lyric on beat 1, short lyric on beat 2. Only the lyric band contributes + * overlay rods, so the bar widens via the lyric-bucket pair-overlap between + * beats 1 and 2. This file is paired with `overlay-rod-multi-band-no-spurious- + * overlap` below: the two renderings MUST produce identical bar widths and + * beat positions. If they drift, the bucketing has broken and cross-band rods + * are once again firing spurious force. + */ + it('overlay-rod-multi-band-no-spurious-overlap-control', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics "widewidewideword x" + \\ts 4 4 + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap-control.png', + settings + ); + }); + + /** + * MULTI-BAND BUCKETING REGRESSION GUARD — multi-band case. + * + * Same as the control above (wide lyric on beat 1, short lyric on beat 2), + * PLUS a wide beat-text annotation on beat 1 (`ALongBeatTextAnnotation`). The + * wide text alone would, under the OLD single-stream design, have max-merged + * with the lyric's rod on beat 1 and inflated the pair-overlap between beats + * 1 and 2 — widening the bar spuriously. With per-band bucketing, the text + * rod lives in the `EffectText` bucket where it has no pair-overlap partner, + * so it contributes NO force. The lyric bucket still drives the same widening + * as the control case. + * + * Expected: bar width and beat positions match the control rendering exactly + * (modulo subpixel differences from the text glyph rendering above the staff). + */ + it('overlay-rod-multi-band-no-spurious-overlap', async () => { + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\lyrics "widewidewideword x" + \\ts 4 4 + :4 c4 {txt "ALongBeatTextAnnotation"} c4 c4 c4 + `, + 'test-data/visual-tests/overlay-rod-spacing/overlay-rod-multi-band-no-spurious-overlap.png', + settings + ); + }); + +}); diff --git a/packages/alphatab/test/visualTests/features/SystemSpacing.test.ts b/packages/alphatab/test/visualTests/features/SystemSpacing.test.ts index 5a562f92a..1b09931d6 100644 --- a/packages/alphatab/test/visualTests/features/SystemSpacing.test.ts +++ b/packages/alphatab/test/visualTests/features/SystemSpacing.test.ts @@ -212,13 +212,16 @@ describe('SystemSpacingTests', () => { }); it('stretch-formula-duration-spacing', async () => { - // A single bar with three distinct durations - half, quarter, two eighths - laid - // out in decreasing order. Under the current log-scale Gourlay formula - // `phi = 1 + 0.85 * log2(duration / minDuration)` the half-note occupies visibly - // more horizontal space than the quarter, which occupies more than each eighth. - // No short notes are involved, so this test also validates the non-phase-B path - // and pins the current formula's proportions: a coefficient change or a switch to - // linear / square-root spacing would shift this baseline. + // A single bar with three distinct durations - half, quarter, two eighths - laid out in + // decreasing order. The power-law formula `phi = (d/dmin)^log2(spacingRatio)` (default + // r=1.5, exponent ≈ 0.585) makes the half-note occupy visibly more horizontal space than + // the quarter, which occupies more than each eighth. The 1:2:4 duration ratio maps to + // 1.0 : 1.5 : 2.25 in `phi`, i.e. successive durations get 1.5x the natural length. + // + // No short notes are involved, so this test also validates the non-phase-B path and pins + // the current formula's proportions: a change to the default `spacingRatio` (or a switch + // back to the additive formula, or to linear / square-root spacing) would shift this + // baseline visibly. const settings = new Settings(); settings.display.layoutMode = LayoutMode.Parchment; await VisualTestHelper.runVisualTestTex( @@ -231,4 +234,229 @@ describe('SystemSpacingTests', () => { settings ); }); + + /** + * Visual coverage for the power-law spacing formula introduced by Phase 1 of the spacing + * spike. Each test in this group pins one specific behaviour of the new formula. + * + * Reference: `docs/spacing/spacing-spike-plan.md` Section 6.3, "Tests to add per phase". + */ + it('power-law-rest-bar-not-ballooning', async () => { + // Two-bar system rendered at default `spacingRatio = 1.5`: + // Bar 1: a dense bar with sixteenth-note runs (60-tick duration). + // Bar 2: a single whole-note rest (3840-tick duration). + // System-wide minDuration resolves to 60 (the sixteenth) so bar 2's rest sits at + // d/dmin = 64 -> phi = 64^log2(1.5) ≈ 16.0. The whole rest is therefore proportionally + // wider than the sixteenths but NOT the runaway 4-5x balloon that the additive formula + // produced when `stretchForce` was raised. + // + // Expected baseline: bar 2 (whole rest) is visibly wider than each beat of bar 1, but + // the two bars stay in roughly comparable widths. Under the OLD additive formula at + // stretchForce > 1, bar 2 would dominate the system width. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + settings.display.stretchForce = 1.5; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 2 } + \\ts 4 4 + :16 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 | + :1 r + `, + 'test-data/visual-tests/system-spacing/power-law-rest-bar-not-ballooning.png', + settings + ); + }); + + it('power-law-duration-proportions', async () => { + // A single bar containing whole, half, quarter, eighth, sixteenth in sequence (decreasing + // duration). With the default `spacingRatio = 1.5`, each successive note allocates 1.5x + // the horizontal space of its half-duration successor (because doubling the duration + // multiplies phi by exactly `spacingRatio`). + // + // Expected baseline: visually the durations form a clean geometric progression in + // horizontal allocation - the whole occupies ~1.5x the half, the half ~1.5x the quarter, + // and so on. This is the classic power-law visual signature used by Dorico/MuseScore. + // + // The bar exceeds 4/4 by design - the durations are placed inside 13/4 to give every + // note its own clean spring without rest padding muddling the comparison. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 13 4 + :1 c4 :2 c4 :4 c4 :8 c4 :16 c4 + `, + 'test-data/visual-tests/system-spacing/power-law-duration-proportions.png', + settings + ); + }); + + it('power-law-stretch-force-orthogonal', async () => { + // Same single-bar score rendered at `stretchForce ∈ {0.5, 1.0, 1.5}` across three runs + // (output stacked into a single canvas via the multi-run mechanism). Each run uses the + // default `spacingRatio = 1.5`. The expected behaviour is that increasing `stretchForce` + // makes everything wider proportionally - the *shape* of the spacing curve (relative + // widths of half / quarter / eighth) stays constant, only the overall scale changes. + // + // This test pins the orthogonality claim from Section 4.6 of the plan: + // `stretchForce` and `spacingRatio` control density and proportionality independently. + // + // Expected baseline: three rendered systems at increasing widths but with identical + // *internal* duration proportions in each. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + settings.display.stretchForce = 0.5; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :2 c4 :4 c4 :8 c4 c4 + `, + 'test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-low.png', + settings + ); + }); + + it('power-law-stretch-force-orthogonal-high', async () => { + // Sibling to `power-law-stretch-force-orthogonal` rendered at `stretchForce = 1.5`. The + // higher force should produce a wider system but with the *same* duration ratios as the + // low-force run. This is the visual proof of orthogonality: changing the force scales + // the system uniformly without distorting the proportions established by `spacingRatio`. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + settings.display.stretchForce = 1.5; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :2 c4 :4 c4 :8 c4 c4 + `, + 'test-data/visual-tests/system-spacing/power-law-stretch-force-orthogonal-high.png', + settings + ); + }); + + it('power-law-spacing-ratio-tight', async () => { + // Same score rendered with `spacingRatio = 1.2` (the tight end of the documented range). + // At r=1.2, exponent = log2(1.2) ≈ 0.263, so doubling the duration multiplies phi by + // 1.2 - durations crowd together more tightly than at the default 1.5. + // + // Expected baseline: the half-note's allocated space is only ~1.2x the quarter (vs. + // ~1.5x at default). Visually this looks more "compressed" than the default. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + settings.display.spacingRatio = 1.2; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :2 c4 :4 c4 :8 c4 c4 + `, + 'test-data/visual-tests/system-spacing/power-law-spacing-ratio-tight.png', + settings + ); + }); + + it('power-law-spacing-ratio-loose', async () => { + // Same score rendered with `spacingRatio = 1.8` (the loose end of the documented range). + // At r=1.8, exponent = log2(1.8) ≈ 0.848, so doubling the duration multiplies phi by 1.8 - + // durations spread further apart than at the default 1.5. + // + // Expected baseline: the half-note's allocated space is ~1.8x the quarter (vs. ~1.5x at + // default). Visually this looks more "open" / "traditional" than the default and closer + // to Finale's Fibonacci-style spacing. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + settings.display.spacingRatio = 1.8; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 1 } + \\ts 4 4 + :2 c4 :4 c4 :8 c4 c4 + `, + 'test-data/visual-tests/system-spacing/power-law-spacing-ratio-loose.png', + settings + ); + }); + + /** + * Visual coverage for Phase 2 (A2) of the spacing spike: the system-wide minimum-duration + * reference (`StaffSystem.minDuration`) must aggregate across tracks and must NOT be + * inflated by grace-note durations. + * + * The pre-existing `shared-min-duration-*` tests cover the core cross-bar behaviour for + * single-track scores; these two tests cover the cases the spike plan explicitly calls out + * in Section 6.3 as needing dedicated coverage. + */ + it('system-min-duration-grace-notes-excluded', async () => { + // Two-bar system. Bar 1 has a 32nd-note grace flourish (60 ticks per grace) before its + // first quarter; bar 2 has only quarters with no graces. Grace-note durations are routed + // through `addBeatSpring`'s grace branch which never calls `addSpring`, so they cannot + // update `BarLayoutingInfo._minDuration`. The system-wide minimum therefore falls back + // to the default reference (30 ticks) - identical to the no-graces case - and the + // quarter-note positions in bar 1 and bar 2 align column-by-column. + // + // If a regression introduced grace durations into `_minDuration`, bar 1's reference + // would drop below bar 2's, the system would reconcile against the smaller value, and + // the quarters would shift visibly between the two bars. + // + // Expected baseline: quarter notes in both bars align under the same x-coordinates. + // Bar 1 has a small grace cluster anchored to the first quarter; bar 2 is plain quarters. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\track { defaultSystemsLayout 2 } + \\ts 4 4 + :32 c5 {gr bb} d5 {gr bb} e5 {gr bb} :4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/system-spacing/system-min-duration-grace-notes-excluded.png', + settings + ); + }); + + it('system-min-duration-multi-track-aggregation', async () => { + // Two tracks rendered together as a multi-track system, two bars per system. + // Track 1, bar 1: sixteenth-note runs (60-tick minimum). + // Track 1, bar 2: only quarters. + // Track 2, bar 1: only quarters. + // Track 2, bar 2: sixteenth-note runs (60-tick minimum). + // + // Bar 1's `BarLayoutingInfo` is shared between Track 1's sixteenths and Track 2's + // quarters - so its local minimum is 60 (set by Track 1). Bar 2's `BarLayoutingInfo` is + // shared between Track 1's quarters and Track 2's sixteenths - so its local minimum is + // also 60 (set by Track 2). Both bars therefore reference the same minimum and the + // quarter-note columns in both bars (across both tracks) line up. + // + // This test pins the cross-track aggregation: the shared `BarLayoutingInfo` per + // `MasterBarsRenderers` ensures every staff at the same bar index contributes to the + // same `_minDuration`. A regression that gave each track its own layouting info would + // produce mismatched per-bar minima and visible misalignment between the rows. + // + // Expected baseline: vertically aligned columns. The quarter notes in track 2 bar 1 sit + // directly above the regular quarter-note positions established by track 1's sixteenth + // grouping, and vice versa for bar 2. + const settings = new Settings(); + settings.display.layoutMode = LayoutMode.Page; + await VisualTestHelper.runVisualTestTex( + ` + \\track "T1" + \\ts 4 4 + :16 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 | + :4 c4 c4 c4 c4 + \\track "T2" + \\ts 4 4 + :4 c4 c4 c4 c4 | + :16 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 c4 + `, + 'test-data/visual-tests/system-spacing/system-min-duration-multi-track-aggregation.png', + settings, + o => { + o.tracks = [0, 1]; + } + ); + }); }); diff --git a/packages/csharp/src/AlphaTab/Core/EcmaScript/Math.cs b/packages/csharp/src/AlphaTab/Core/EcmaScript/Math.cs index 23b261603..28726aa83 100644 --- a/packages/csharp/src/AlphaTab/Core/EcmaScript/Math.cs +++ b/packages/csharp/src/AlphaTab/Core/EcmaScript/Math.cs @@ -8,6 +8,7 @@ internal static class Math { private static readonly Random Rnd = new Random(); public static double PI => System.Math.PI; + public const double SQRT2 = 1.4142135623730951; public static double Random() { diff --git a/packages/kotlin/src/android/src/main/java/alphaTab/core/ecmaScript/Math.kt b/packages/kotlin/src/android/src/main/java/alphaTab/core/ecmaScript/Math.kt index 7b5427ff7..23d1a4e43 100644 --- a/packages/kotlin/src/android/src/main/java/alphaTab/core/ecmaScript/Math.kt +++ b/packages/kotlin/src/android/src/main/java/alphaTab/core/ecmaScript/Math.kt @@ -6,6 +6,7 @@ import kotlin.math.roundToInt internal class Math { companion object { public const val PI: Double = kotlin.math.PI + public const val SQRT2: Double = 1.4142135623730951; public fun log10(x: Double): Double { return kotlin.math.log10(x) } diff --git a/packages/playground/demos/test-results/index.ts b/packages/playground/demos/test-results/index.ts index 69752e3b4..95a8cbf6f 100644 --- a/packages/playground/demos/test-results/index.ts +++ b/packages/playground/demos/test-results/index.ts @@ -6,3 +6,10 @@ if (!root) { } const app = new TestResultsApp(); root.appendChild(app.root); + +if (import.meta.hot) { + import.meta.hot.accept(); + import.meta.hot.dispose(() => { + app.dispose(); + }); +} diff --git a/packages/playground/src/apps/TestResultsApp.ts b/packages/playground/src/apps/TestResultsApp.ts index 35da520b2..5041cbce7 100644 --- a/packages/playground/src/apps/TestResultsApp.ts +++ b/packages/playground/src/apps/TestResultsApp.ts @@ -2,6 +2,7 @@ import { ByteBuffer } from '@coderline/alphatab/io/ByteBuffer'; import { ZipReader } from '@coderline/alphatab/zip/ZipReader'; import { NavMenu } from '../components/NavMenu'; import { type Mountable, css, html, injectStyles, parseHtml } from '../util/Dom'; +import { Settings } from '@coderline/alphatab'; injectStyles( 'TestResultsApp', @@ -32,10 +33,46 @@ injectStyles( font-size: 1rem; margin: 0 0 8px 0; } - .at-test-comparer { position: relative; } + .at-test-comparer { + position: relative; + display: inline-grid; + grid-template-columns: max-content; + vertical-align: top; + } + .at-test-comparer .expected, + .at-test-comparer .actual, + .at-test-comparer .diff { + grid-column: 1; + grid-row: 1; + align-self: start; + justify-self: start; + display: block; + border: 1px solid red; + max-width: none; + } + .at-test-comparer .expected { + clip-path: inset(0 50% 0 0); + } + .at-test-comparer .actual { + clip-path: inset(0 0 0 50%); + } + .at-test-comparer .diff { + display: none; + } + .at-test-card.show-diff .at-test-comparer .expected, + .at-test-card.show-diff .at-test-comparer .actual, + .at-test-card.show-diff .at-test-comparer .slider-handle { display: none; } + .at-test-card.show-diff .at-test-comparer .diff { display: block; } + .at-test-card.accepted .expected, + .at-test-card.accepted .actual, + .at-test-card.accepted .diff { border-color: green; } + body.hide-accepted .at-test-card.accepted { display: none; } + .at-test-comparer .slider-handle { position: absolute; + top: 0; bottom: 0; + left: 50%; width: 40px; transform: translateX(-50%); cursor: ew-resize; @@ -59,10 +96,10 @@ injectStyles( content: ''; position: sticky; top: calc(50vh - 20px); + bottom: calc(50vh - 20px); display: block; width: 40px; height: 40px; - margin-top: var(--knob-margin-top, 0); background-color: #fff; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 18L3 12l6-6M15 6l6 6-6 6' fill='none' stroke='%23555' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); background-repeat: no-repeat; @@ -76,34 +113,6 @@ injectStyles( .at-test-comparer .slider-handle:hover::after { box-shadow: 0 3px 12px rgba(0, 0, 0, 0.35), 0 0 0 1.5px rgba(0, 0, 0, 0.12); } - .at-test-comparer .expected, - .at-test-comparer .actual, - .at-test-comparer .diff { - background: #fff; - border: 1px solid red; - position: absolute; - } - .at-test-comparer .expected { left: 0; } - .at-test-comparer .actual { - right: -2px; - box-shadow: -7px 0 10px -5px rgba(0, 0, 0, 0.5); - overflow: hidden; - border-left: 0; - } - .at-test-comparer .actual img { - position: absolute; - right: 0; - top: 0; - border-left: 1px solid red; - } - .at-test-comparer .diff { - display: none; - left: 0; - } - .at-test-card.accepted .diff, - .at-test-card.accepted .expected, - .at-test-card.accepted .actual { border-color: green; } - body.hide-accepted .at-test-card.accepted { display: none; } .at-test-controls { position: sticky; @@ -172,7 +181,9 @@ export class TestResultsApp implements Mountable { private listEl: HTMLElement; private remainingEl: HTMLElement; private currentResults: TestResult[] = []; + private blobUrls: string[] = []; private nav: NavMenu; + private abort = new AbortController(); private onDragOver = (e: DragEvent) => { e.stopPropagation(); @@ -223,14 +234,20 @@ export class TestResultsApp implements Mountable { private async loadFromServer(): Promise { try { - const res = await fetch('/test-results/list'); + const res = await fetch('/test-results/list', { signal: this.abort.signal }); const list = (await res.json()) as TestResult[]; + if (this.abort.signal.aborted) { + return; + } this.displayResults(list); - } catch { + } catch (e) { + if ((e as Error).name === 'AbortError') { + return; + } alert('error loading test results'); - } - } - + } + } + private updateRemaining(): void { if (this.currentResults.length === 0) { this.remainingEl.textContent = ''; @@ -240,8 +257,15 @@ export class TestResultsApp implements Mountable { this.remainingEl.textContent = `(${remaining}/${this.currentResults.length})`; } - private async displayResults(results: TestResult[]): Promise { + private displayResults(results: TestResult[]): void { + if (this.abort.signal.aborted) { + return; + } this.listEl.replaceChildren(); + for (const url of this.blobUrls) { + URL.revokeObjectURL(url); + } + this.blobUrls = []; this.currentResults = results; if (results.length === 0) { const banner = parseHtml(html`
No reported errors on visual tests.
`); @@ -250,12 +274,15 @@ export class TestResultsApp implements Mountable { return; } for (const result of results) { - this.listEl.appendChild(await this.createResultCard(result)); + this.listEl.appendChild(this.createResultCard(result)); } this.updateRemaining(); } - private async createResultCard(result: TestResult): Promise { + private createResultCard(result: TestResult): HTMLElement { + const expectedSrc = this.toSrc(result.originalFile); + const actualSrc = this.toSrc(result.newFile); + const card = parseHtml(html`
${result.originalFile}
@@ -264,56 +291,61 @@ export class TestResultsApp implements Mountable {
-
expected
-
actual
-
diff
+ expected + actual + diff
`); const comparer = card.querySelector('.at-test-comparer')!; - const ex = comparer.querySelector('.expected')!; - const ac = comparer.querySelector('.actual')!; - const df = comparer.querySelector('.diff')!; + const actual = comparer.querySelector('.actual')!; + const expected = comparer.querySelector('.expected')!; + const diffImg = comparer.querySelector('.diff')!; const handle = comparer.querySelector('.slider-handle')!; - const exImg = ex.querySelector('img')!; - const acImg = ac.querySelector('img')!; - const dfImg = df.querySelector('img')!; - await Promise.allSettled([ - loadImage(exImg, result.originalFile), - loadImage(acImg, result.newFile), - loadImage(dfImg, result.diffFile) - ]); - - const width = Math.max(exImg.width, acImg.width); - const height = Math.max(exImg.height, acImg.height); - comparer.style.width = `${width}px`; - comparer.style.height = `${height}px`; - ex.style.width = `${width}px`; - ex.style.height = `${height}px`; - ac.style.width = `${width / 2}px`; - ac.style.height = `${height}px`; - df.style.width = `${width}px`; - df.style.height = `${height}px`; - - handle.style.left = `${width / 2}px`; - handle.style.setProperty('--knob-margin-top', `${height / 2 - 20}px`); + let fraction = 0.5; + const applyClip = (): void => { + const width = comparer.getBoundingClientRect().width; + if (width === 0) { + return; + } + const x = fraction * width; + const expectedRight = Math.max(0, expected.clientWidth - x); + expected.style.clipPath = `inset(0 ${expectedRight}px 0 0)`; + actual.style.clipPath = `inset(0 0 0 ${x}px)`; + handle.style.left = `${x}px`; + }; + new ResizeObserver(applyClip).observe(comparer); handle.addEventListener('pointerdown', e => { handle.setPointerCapture(e.pointerId); e.preventDefault(); }); handle.addEventListener('pointermove', e => { - if (!e.buttons) { return; } + if (!e.buttons) { + return; + } const rect = comparer.getBoundingClientRect(); - const x = Math.max(0, Math.min(e.clientX - rect.left, width)); - handle.style.left = `${x}px`; - ac.style.width = `${width - x}px`; + if (rect.width === 0) { + return; + } + const x = Math.max(0, Math.min(e.clientX - rect.left, rect.width)); + fraction = x / rect.width; + applyClip(); }); + card.querySelector('.diff-toggle')!.onchange = e => { - df.style.display = (e.target as HTMLInputElement).checked ? 'block' : 'none'; + const show = (e.target as HTMLInputElement).checked; + card.classList.toggle('show-diff', show); + if (show && !diffImg.src) { + const src = this.toSrc(result.diffFile); + if (src) { + diffImg.src = src; + } + } }; + const acceptBtn = card.querySelector('.accept')!; acceptBtn.onclick = async () => { acceptBtn.disabled = true; @@ -338,6 +370,18 @@ export class TestResultsApp implements Mountable { return card; } + private toSrc(source: string | Uint8Array): string { + if (source instanceof Uint8Array) { + const url = URL.createObjectURL(new Blob([source.buffer as ArrayBuffer], { type: 'image/png' })); + this.blobUrls.push(url); + return url; + } + if (typeof source === 'string' && source.length > 0) { + return `/${source}`; + } + return ''; + } + private handleDrop(e: DragEvent): void { e.stopPropagation(); e.preventDefault(); @@ -352,7 +396,7 @@ export class TestResultsApp implements Mountable { if (!(buffer instanceof ArrayBuffer)) { return; } - const zip = new ZipReader(ByteBuffer.fromBuffer(new Uint8Array(buffer)), 128000000); + const zip = new ZipReader(ByteBuffer.fromBuffer(new Uint8Array(buffer)), new Settings().importer.maxDecodingBufferSize); const entries = zip.read(); const grouped = new Map(); for (const entry of entries) { @@ -378,25 +422,16 @@ export class TestResultsApp implements Mountable { } dispose(): void { + this.abort.abort(); document.body.removeEventListener('dragover', this.onDragOver, false); document.body.removeEventListener('dragenter', this.onDragEnter, true); document.body.removeEventListener('dragleave', this.onDragLeave); document.body.removeEventListener('drop', this.onDrop, true); + for (const url of this.blobUrls) { + URL.revokeObjectURL(url); + } + this.blobUrls = []; this.nav.dispose(); this.root.remove(); } } - -function loadImage(img: HTMLImageElement, source: string | Uint8Array): Promise { - return new Promise((resolve, reject) => { - img.onload = () => resolve(); - img.onerror = () => reject(); - if (source instanceof Uint8Array) { - img.src = URL.createObjectURL(new Blob([source.buffer as ArrayBuffer], { type: 'image/png' })); - } else if (typeof source === 'string' && source.length > 0) { - img.src = `/${source}`; - } else { - reject(); - } - }); -} diff --git a/packages/playground/src/util/Dom.ts b/packages/playground/src/util/Dom.ts index 22ec2c478..63806cd86 100644 --- a/packages/playground/src/util/Dom.ts +++ b/packages/playground/src/util/Dom.ts @@ -52,6 +52,11 @@ export function injectStyles(key: string, sheet: string): void { el = document.createElement('style'); el.dataset.cmp = key; document.head.appendChild(el); + } else if (el.textContent === sheet) { + // Re-running the module on HMR but the stylesheet text is identical — skip the write + // to avoid invalidating computed styles for every element matching any selector in + // this sheet (which on a long list of cards causes a full-document layout thrash). + return; } el.textContent = sheet; } diff --git a/packages/playground/vite.config.ts b/packages/playground/vite.config.ts index 61c38a918..5bcea1c67 100644 --- a/packages/playground/vite.config.ts +++ b/packages/playground/vite.config.ts @@ -1,10 +1,14 @@ import { defineConfig, type UserConfig } from 'vite'; +import { buildTsconfigAliases } from '../tooling/src/vite'; import { elementStyleUsingPlugin } from '../tooling/src/vite.plugin.transform'; import server from './vite.plugin.server'; export default defineConfig(_ => { const config: UserConfig = { plugins: [server(), elementStyleUsingPlugin()], + resolve: { + alias: buildTsconfigAliases(__dirname) + }, server: { open: '/index.html' }