From 7b7c85219ae5a8798ecd18a2e63dccfb6e2be3db Mon Sep 17 00:00:00 2001 From: kulek Date: Sun, 21 Jun 2026 09:30:28 +0200 Subject: [PATCH 1/9] feat: add inline tuning --- .../model/RenderStylesheetSerializer.ts | 5 + .../alphaTex/AlphaTex1EnumMappings.ts | 10 +- .../alphaTex/AlphaTex1LanguageDefinitions.ts | 2 + .../alphaTex/AlphaTex1LanguageHandler.ts | 22 +++- .../alphatab/src/model/RenderStylesheet.ts | 21 ++++ packages/alphatab/src/model/_barrel.ts | 1 + .../src/rendering/layout/ScoreLayout.ts | 7 +- .../src/rendering/staves/StaffSystem.ts | 106 +++++++++++++++++- .../test/importer/AlphaTexImporter.test.ts | 5 +- packages/alphatex/src/definitions.ts | 2 + packages/alphatex/src/enum.ts | 5 + .../src/metadata/score/tuningdisplaymode.ts | 27 +++++ 12 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 packages/alphatex/src/metadata/score/tuningdisplaymode.ts diff --git a/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts b/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts index 10ac9a9ac..1f3a17c33 100644 --- a/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts +++ b/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts @@ -6,6 +6,7 @@ import { RenderStylesheet } from "@coderline/alphatab/model/RenderStylesheet"; import { JsonHelper } from "@coderline/alphatab/io/JsonHelper"; import { BracketExtendMode } from "@coderline/alphatab/model/RenderStylesheet"; +import { TuningDisplayMode } from "@coderline/alphatab/model/RenderStylesheet"; import { TrackNamePolicy } from "@coderline/alphatab/model/RenderStylesheet"; import { TrackNameMode } from "@coderline/alphatab/model/RenderStylesheet"; import { TrackNameOrientation } from "@coderline/alphatab/model/RenderStylesheet"; @@ -29,6 +30,7 @@ export class RenderStylesheetSerializer { o.set("bracketextendmode", obj.bracketExtendMode as number); o.set("usesystemsignseparator", obj.useSystemSignSeparator); o.set("globaldisplaytuning", obj.globalDisplayTuning); + o.set("tuningdisplaymode", obj.tuningDisplayMode as number); if (obj.perTrackDisplayTuning !== null) { const m = new Map(); o.set("pertrackdisplaytuning", m); @@ -80,6 +82,9 @@ export class RenderStylesheetSerializer { case "globaldisplaytuning": obj.globalDisplayTuning = v! as boolean; return true; + case "tuningdisplaymode": + obj.tuningDisplayMode = JsonHelper.parseEnum(v, TuningDisplayMode)!; + return true; case "pertrackdisplaytuning": obj.perTrackDisplayTuning = new Map(); JsonHelper.forEach(v, (v, k) => { diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts index 598cad9ef..a3fe7fab7 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts @@ -18,7 +18,8 @@ import type { BracketExtendMode, TrackNameMode, TrackNameOrientation, - TrackNamePolicy + TrackNamePolicy, + TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import type { SimileMark } from '@coderline/alphatab/model/SimileMark'; import type { TremoloPickingStyle } from '@coderline/alphatab/model/TremoloPickingEffect'; @@ -198,6 +199,13 @@ export class AlphaTex1EnumMappings { ['shortname', 1] ]); public static readonly trackNameModeReversed = AlphaTex1EnumMappings._reverse(AlphaTex1EnumMappings.trackNameMode); + public static readonly tuningDisplayMode = new Map([ + ['score', 0], + ['staff', 1] + ]); + public static readonly tuningDisplayModeReversed = AlphaTex1EnumMappings._reverse( + AlphaTex1EnumMappings.tuningDisplayMode + ); public static readonly textAlign = new Map([ ['left', 0], ['center', 1], diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts index 8e84fe7ed..520fbf79f 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts @@ -181,6 +181,7 @@ export class AlphaTex1LanguageDefinitions { ['showdynamics', null], ['hidedynamics', null], ['usesystemsignseparator', null], + ['tuningdisplaymode', [[[[10, 17], 0, ['score', 'staff']]]]], ['multibarrest', null], ['bracketextendmode', [[[[10, 17], 0, ['nobrackets', 'groupstaves', 'groupsimilarinstruments']]]]], ['singletracktracknamepolicy', [[[[10, 17], 0, ['hidden', 'firstsystem', 'allsystems']]]]], @@ -534,6 +535,7 @@ export class AlphaTex1LanguageDefinitions { ['showdynamics', null], ['hidedynamics', null], ['usesystemsignseparator', null], + ['tuningdisplaymode', null], ['multibarrest', null], ['bracketextendmode', null], ['singletracktracknamepolicy', null], diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts index 4b38647d0..1608567fa 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts @@ -71,7 +71,7 @@ import { NoteOrnament } from '@coderline/alphatab/model/NoteOrnament'; import { Ottavia } from '@coderline/alphatab/model/Ottavia'; import { PercussionMapper } from '@coderline/alphatab/model/PercussionMapper'; import { PickStroke } from '@coderline/alphatab/model/PickStroke'; -import { BarNumberDisplay, type RenderStylesheet } from '@coderline/alphatab/model/RenderStylesheet'; +import { BarNumberDisplay, type RenderStylesheet, TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import { HeaderFooterStyle, Score, ScoreStyle, ScoreSubElement } from '@coderline/alphatab/model/Score'; import { Section } from '@coderline/alphatab/model/Section'; import { SimileMark } from '@coderline/alphatab/model/SimileMark'; @@ -188,6 +188,18 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler case 'usesystemsignseparator': score.stylesheet.useSystemSignSeparator = true; return ApplyNodeResult.Applied; + case 'tuningdisplaymode': + const tuningDisplayMode = AlphaTex1LanguageHandler._parseEnumValue( + importer, + metaData.arguments!, + 'tuning display mode', + AlphaTex1EnumMappings.tuningDisplayMode + ); + if (tuningDisplayMode === undefined) { + return ApplyNodeResult.NotAppliedSemanticError; + } + score.stylesheet.tuningDisplayMode = tuningDisplayMode!; + return ApplyNodeResult.Applied; case 'multibarrest': score.stylesheet.multiTrackMultiBarRest = true; return ApplyNodeResult.Applied; @@ -2562,6 +2574,14 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler if (stylesheet.useSystemSignSeparator) { nodes.push(Atnf.meta('useSystemSignSeparator')); } + if (stylesheet.tuningDisplayMode !== TuningDisplayMode.Score) { + nodes.push( + Atnf.identMeta( + 'tuningDisplayMode', + AlphaTex1EnumMappings.tuningDisplayModeReversed.get(stylesheet.tuningDisplayMode)! + ) + ); + } if (stylesheet.multiTrackMultiBarRest) { nodes.push(Atnf.meta('multiBarRest')); } diff --git a/packages/alphatab/src/model/RenderStylesheet.ts b/packages/alphatab/src/model/RenderStylesheet.ts index 81e4a4ad5..a0ad180f2 100644 --- a/packages/alphatab/src/model/RenderStylesheet.ts +++ b/packages/alphatab/src/model/RenderStylesheet.ts @@ -72,6 +72,22 @@ export enum TrackNameOrientation { Vertical = 1 } +/** + * Lists the different places where string tuning information is displayed. + * @public + */ +export enum TuningDisplayMode { + /** + * Tuning information is displayed above the score. + */ + Score = 0, + + /** + * Tuning note names are displayed beside the corresponding tab staff lines. + */ + Staff = 1 +} + /** * How bar numbers are displayed * @public @@ -119,6 +135,11 @@ export class RenderStylesheet { */ public globalDisplayTuning: boolean = true; + /** + * The place where tuning information is displayed. + */ + public tuningDisplayMode: TuningDisplayMode = TuningDisplayMode.Score; + /** * Whether to show the tuning.(per-track) */ diff --git a/packages/alphatab/src/model/_barrel.ts b/packages/alphatab/src/model/_barrel.ts index c5c0e1a6a..94dea8678 100644 --- a/packages/alphatab/src/model/_barrel.ts +++ b/packages/alphatab/src/model/_barrel.ts @@ -51,6 +51,7 @@ export { TrackNamePolicy, TrackNameMode, TrackNameOrientation, + TuningDisplayMode, BarNumberDisplay } from '@coderline/alphatab/model/RenderStylesheet'; export { RepeatGroup } from '@coderline/alphatab/model/RepeatGroup'; diff --git a/packages/alphatab/src/rendering/layout/ScoreLayout.ts b/packages/alphatab/src/rendering/layout/ScoreLayout.ts index cc6585301..b821d1435 100644 --- a/packages/alphatab/src/rendering/layout/ScoreLayout.ts +++ b/packages/alphatab/src/rendering/layout/ScoreLayout.ts @@ -4,6 +4,7 @@ import { Logger } from '@coderline/alphatab/Logger'; import type { Bar } from '@coderline/alphatab/model/Bar'; import { Font, FontStyle, FontWeight } from '@coderline/alphatab/model/Font'; import { ModelUtils } from '@coderline/alphatab/model/ModelUtils'; +import { TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import { type Score, ScoreStyle, ScoreSubElement } from '@coderline/alphatab/model/Score'; import type { Staff } from '@coderline/alphatab/model/Staff'; import { type Track, TrackSubElement } from '@coderline/alphatab/model/Track'; @@ -305,7 +306,11 @@ export abstract class ScoreLayout { } } // tuning info - if (stavesWithTuning.length > 0 && score.stylesheet.globalDisplayTuning) { + if ( + stavesWithTuning.length > 0 && + score.stylesheet.globalDisplayTuning && + score.stylesheet.tuningDisplayMode === TuningDisplayMode.Score + ) { this.tuningGlyph = new TuningContainerGlyph(0, 0); this.tuningGlyph.renderer = fakeBarRenderer; for (const staff of stavesWithTuning) { diff --git a/packages/alphatab/src/rendering/staves/StaffSystem.ts b/packages/alphatab/src/rendering/staves/StaffSystem.ts index 1fa922a1f..eff3a1c64 100644 --- a/packages/alphatab/src/rendering/staves/StaffSystem.ts +++ b/packages/alphatab/src/rendering/staves/StaffSystem.ts @@ -6,15 +6,18 @@ import { BracketExtendMode, TrackNameMode, TrackNameOrientation, - TrackNamePolicy + TrackNamePolicy, + TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import { type Track, TrackSubElement } from '@coderline/alphatab/model/Track'; +import { Tuning } from '@coderline/alphatab/model/Tuning'; import { NotationElement } from '@coderline/alphatab/NotationSettings'; import { CanvasHelper, type ICanvas, TextAlign, TextBaseline } from '@coderline/alphatab/platform/ICanvas'; import type { RenderingResources } from '@coderline/alphatab/RenderingResources'; import type { BarRendererBase } from '@coderline/alphatab/rendering/BarRendererBase'; import type { LineBarRenderer } from '@coderline/alphatab/rendering/LineBarRenderer'; import type { ScoreLayout } from '@coderline/alphatab/rendering/layout/ScoreLayout'; +import { TabBarRenderer } from '@coderline/alphatab/rendering/TabBarRenderer'; import { BarLayoutingInfo } from '@coderline/alphatab/rendering/staves/BarLayoutingInfo'; import { MasterBarsRenderers } from '@coderline/alphatab/rendering/staves/MasterBarsRenderers'; import type { RenderStaff } from '@coderline/alphatab/rendering/staves/RenderStaff'; @@ -657,6 +660,8 @@ export class StaffSystem { } } + this.accoladeWidth += this._getInlineTuningWidth(this.allStaves, this.layout.renderer.canvas!); + // NOTE: we have a chicken-egg problem when it comes to scaling braces which we try to mitigate here: // - The brace scales with the height of the system // - The height of the system depends on the bars which can be fitted @@ -696,6 +701,63 @@ export class StaffSystem { } } + private _shouldRenderInlineTuning(staff: RenderStaff): boolean { + const score = this.layout.renderer.score!; + if ( + this.index !== 0 || + !staff.isVisible || + staff.staffId !== TabBarRenderer.StaffId || + !this.layout.renderer.settings.notation.isNotationElementVisible(NotationElement.GuitarTuning) || + !score.stylesheet.globalDisplayTuning || + score.stylesheet.tuningDisplayMode !== TuningDisplayMode.Staff + ) { + return false; + } + + const modelStaff = staff.modelStaff; + if ( + modelStaff.isPercussion || + !modelStaff.isStringed || + !modelStaff.showTablature || + modelStaff.stringTuning.tunings.length === 0 + ) { + return false; + } + + const perTrackDisplayTuning = score.stylesheet.perTrackDisplayTuning; + return ( + !perTrackDisplayTuning || + !perTrackDisplayTuning.has(modelStaff.track.index) || + perTrackDisplayTuning.get(modelStaff.track.index) !== false + ); + } + + private _getInlineTuningWidth(staves: Iterable, canvas: ICanvas): number { + const oldFont = canvas.font; + canvas.font = this.layout.renderer.settings.display.resources.elementFonts.get(NotationElement.GuitarTuning)!; + + let maxWidth = 0; + for (const staff of staves) { + if (!this._shouldRenderInlineTuning(staff)) { + continue; + } + + for (const tuning of staff.modelStaff.stringTuning.tunings) { + maxWidth = Math.max(maxWidth, canvas.measureText(Tuning.getTextForTuning(tuning, false)).width); + } + } + canvas.font = oldFont; + + return maxWidth > 0 ? maxWidth + this.layout.renderer.settings.display.inlineTuningPaddingRight : 0; + } + + private _getInlineTuningWidthForBracket(bracket: SystemBracket, canvas: ICanvas): number { + return this._getInlineTuningWidth( + this.allStaves.filter(staff => bracket.includesStaff(staff)), + canvas + ); + } + private _getStaffTrackGroup(track: Track): StaffTrackGroup | null { for (let i: number = 0, j: number = this.staves.length; i < j; i++) { const g: StaffTrackGroup = this.staves[i]; @@ -874,6 +936,7 @@ export class StaffSystem { g.staves[0].x - // left side of the bracket settings.display.accoladeBarPaddingRight - + this._getInlineTuningWidth(g.staves, canvas) - (g.bracket?.width ?? 0) - // padding between label and bracket settings.display.systemLabelPaddingRight; @@ -909,6 +972,8 @@ export class StaffSystem { } } + this._paintInlineTunings(cx, cy, canvas); + const needsSystemBarLine = !this.layout.renderer.score!.stylesheet.extendBarLines; if (this.allStaves.length > 0 && needsSystemBarLine) { let previousStaffInBracket: RenderStaff | null = null; @@ -944,6 +1009,42 @@ export class StaffSystem { } } + private _paintInlineTunings(cx: number, cy: number, canvas: ICanvas): void { + const oldFont = canvas.font; + const oldBaseLine = canvas.textBaseline; + const oldTextAlign = canvas.textAlign; + + canvas.font = this.layout.renderer.settings.display.resources.elementFonts.get(NotationElement.GuitarTuning)!; + canvas.textBaseline = TextBaseline.Middle; + canvas.textAlign = TextAlign.Right; + + for (const staff of this.allStaves) { + if (!this._shouldRenderInlineTuning(staff)) { + continue; + } + + const renderer = staff.barRenderers[0]; + if (!(renderer instanceof TabBarRenderer)) { + continue; + } + const textEndX = cx + staff.x - this.layout.renderer.settings.display.inlineTuningPaddingRight; + + using _ = ElementStyleHelper.track(canvas, TrackSubElement.StringTuning, staff.modelStaff.track, true); + + for (let i = 0, j = staff.modelStaff.stringTuning.tunings.length; i < j; i++) { + canvas.fillText( + Tuning.getTextForTuning(staff.modelStaff.stringTuning.tunings[i], false), + textEndX, + cy + staff.y + renderer.y + renderer.getLineY(i) + ); + } + } + + canvas.font = oldFont; + canvas.textBaseline = oldBaseLine; + canvas.textAlign = oldTextAlign; + } + private _paintBrackets(cx: number, cy: number, canvas: ICanvas) { const settings = this.layout.renderer.settings; @@ -951,7 +1052,8 @@ export class StaffSystem { if (bracket.canPaint) { const barStartX: number = cx + bracket.firstVisibleStaffInBracket!.x; const barSize: number = bracket.width; - const barOffset: number = settings.display.accoladeBarPaddingRight; + const barOffset: number = + settings.display.accoladeBarPaddingRight + this._getInlineTuningWidthForBracket(bracket, canvas); const firstStart: number = cy + bracket.firstVisibleStaffInBracket!.contentTop; const lastEnd: number = cy + bracket.lastVisibleStaffInBracket!.contentBottom; let accoladeStart: number = firstStart; diff --git a/packages/alphatab/test/importer/AlphaTexImporter.test.ts b/packages/alphatab/test/importer/AlphaTexImporter.test.ts index 39a76e468..ace28cc66 100644 --- a/packages/alphatab/test/importer/AlphaTexImporter.test.ts +++ b/packages/alphatab/test/importer/AlphaTexImporter.test.ts @@ -32,7 +32,8 @@ import { BracketExtendMode, TrackNameMode, TrackNameOrientation, - TrackNamePolicy + TrackNamePolicy, + TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import { type Score, ScoreSubElement } from '@coderline/alphatab/model/Score'; import { SimileMark } from '@coderline/alphatab/model/SimileMark'; @@ -1856,6 +1857,7 @@ describe('AlphaTexImporterTest', () => { \\hideDynamics \\bracketExtendMode nobrackets \\useSystemSignSeparator + \\tuningDisplayMode staff \\singleTrackTrackNamePolicy allsystems \\multiTrackTrackNamePolicy Hidden \\firstSystemTrackNameMode fullname @@ -1873,6 +1875,7 @@ describe('AlphaTexImporterTest', () => { expect(score.stylesheet.hideDynamics).toBe(true); expect(score.stylesheet.bracketExtendMode).toBe(BracketExtendMode.NoBrackets); expect(score.stylesheet.useSystemSignSeparator).toBe(true); + expect(score.stylesheet.tuningDisplayMode).toBe(TuningDisplayMode.Staff); expect(score.stylesheet.singleTrackTrackNamePolicy).toBe(TrackNamePolicy.AllSystems); expect(score.stylesheet.multiTrackTrackNamePolicy).toBe(TrackNamePolicy.Hidden); expect(score.stylesheet.firstSystemTrackNameMode).toBe(TrackNameMode.FullName); diff --git a/packages/alphatex/src/definitions.ts b/packages/alphatex/src/definitions.ts index 1e721ca30..14b425f06 100644 --- a/packages/alphatex/src/definitions.ts +++ b/packages/alphatex/src/definitions.ts @@ -43,6 +43,7 @@ import { subtitle } from '@coderline/alphatab-alphatex//metadata/score/subtitle' import { systemsLayout } from '@coderline/alphatab-alphatex//metadata/score/systemslayout'; import { tab } from '@coderline/alphatab-alphatex//metadata/score/tab'; import { title } from '@coderline/alphatab-alphatex//metadata/score/title'; +import { tuningDisplayMode } from '@coderline/alphatab-alphatex//metadata/score/tuningdisplaymode'; import { useSystemSignSeparator } from '@coderline/alphatab-alphatex//metadata/score/usesystemsignseparator'; import { words } from '@coderline/alphatab-alphatex//metadata/score/words'; import { wordsAndMusic } from '@coderline/alphatab-alphatex//metadata/score/wordsandmusic'; @@ -178,6 +179,7 @@ export const scoreMetaData = metadata( showDynamics, hideDynamics, useSystemSignSeparator, + tuningDisplayMode, multiBarRest, bracketExtendMode, singleTrackTrackNamePolicy, diff --git a/packages/alphatex/src/enum.ts b/packages/alphatex/src/enum.ts index 014c0516e..65331f264 100644 --- a/packages/alphatex/src/enum.ts +++ b/packages/alphatex/src/enum.ts @@ -19,6 +19,7 @@ export const alphaTexMappedEnumLookup = { TrackNamePolicy: alphaTab.model.TrackNamePolicy, TrackNameOrientation: alphaTab.model.TrackNameOrientation, TrackNameMode: alphaTab.model.TrackNameMode, + TuningDisplayMode: alphaTab.model.TuningDisplayMode, TextAlign: alphaTab.platform.TextAlign, BendType: alphaTab.model.BendType, KeySignature: alphaTab.model.KeySignature, @@ -231,6 +232,10 @@ export const alphaTexMappedEnumMapping: { FullName: { snippet: 'fullName', shortDescription: 'Short track names (abbreviations) are displayed.' }, ShortName: { snippet: 'shortName', shortDescription: 'Full track names are displayed.' } }, + TuningDisplayMode: { + Score: { snippet: 'score', shortDescription: 'Display tuning information above the score.' }, + Staff: { snippet: 'staff', shortDescription: 'Display tuning note names beside tab staff lines.' } + }, TextAlign: { Left: { snippet: 'left', diff --git a/packages/alphatex/src/metadata/score/tuningdisplaymode.ts b/packages/alphatex/src/metadata/score/tuningdisplaymode.ts new file mode 100644 index 000000000..5fe8276f7 --- /dev/null +++ b/packages/alphatex/src/metadata/score/tuningdisplaymode.ts @@ -0,0 +1,27 @@ +import * as alphaTab from '@coderline/alphatab'; +import { enumParameter } from '@coderline/alphatab-alphatex/enum'; +import type { MetadataTagDefinition } from '@coderline/alphatab-alphatex/types'; + +export const tuningDisplayMode: MetadataTagDefinition = { + tag: '\\tuningDisplayMode', + snippet: '\\tuningDisplayMode ${1:score}$0', + shortDescription: 'Sets where string tuning information is displayed.', + signatures: [ + { + parameters: [ + { + name: 'mode', + shortDescription: 'The mode to use', + parseMode: alphaTab.importer.alphaTex.ArgumentListParseTypesMode.Required, + ...enumParameter('TuningDisplayMode') + } + ] + } + ], + examples: ` + \\tuningDisplayMode staff + \\track "Guitar" + \\staff { score tabs } + 0.6.4 2.6.4 3.6.4 0.5.4 + ` +}; From f0185d011665b9217de21e704a7137fbd7f44fa5 Mon Sep 17 00:00:00 2001 From: kulek Date: Sun, 21 Jun 2026 09:30:48 +0200 Subject: [PATCH 2/9] feat: allow to adjust tuning padding --- packages/alphatab/src/DisplaySettings.ts | 8 ++++++++ packages/alphatab/src/generated/DisplaySettingsJson.ts | 7 +++++++ .../alphatab/src/generated/DisplaySettingsSerializer.ts | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/packages/alphatab/src/DisplaySettings.ts b/packages/alphatab/src/DisplaySettings.ts index 4fce2772d..52198ecd2 100644 --- a/packages/alphatab/src/DisplaySettings.ts +++ b/packages/alphatab/src/DisplaySettings.ts @@ -248,6 +248,14 @@ export class DisplaySettings { */ public accoladeBarPaddingRight: number = 3; + /** + * The padding between inline tuning labels and the start of the tab staff. + * @since 1.9.0 + * @category Display + * @defaultValue `5` + */ + public inlineTuningPaddingRight: number = 5; + // Staff padding /** diff --git a/packages/alphatab/src/generated/DisplaySettingsJson.ts b/packages/alphatab/src/generated/DisplaySettingsJson.ts index 20cb54ddb..4a9a493b4 100644 --- a/packages/alphatab/src/generated/DisplaySettingsJson.ts +++ b/packages/alphatab/src/generated/DisplaySettingsJson.ts @@ -218,6 +218,13 @@ export interface DisplaySettingsJson { * @defaultValue `3` */ accoladeBarPaddingRight?: number; + /** + * The padding between inline tuning labels and the start of the tab staff. + * @since 1.9.0 + * @category Display + * @defaultValue `5` + */ + inlineTuningPaddingRight?: number; /** * The top padding applied to the first main notation staff (standard, tabs, numbered, slash). * @since 1.8.0 diff --git a/packages/alphatab/src/generated/DisplaySettingsSerializer.ts b/packages/alphatab/src/generated/DisplaySettingsSerializer.ts index c07b064b7..d715e8f1e 100644 --- a/packages/alphatab/src/generated/DisplaySettingsSerializer.ts +++ b/packages/alphatab/src/generated/DisplaySettingsSerializer.ts @@ -42,6 +42,7 @@ export class DisplaySettingsSerializer { o.set("systemlabelpaddingleft", obj.systemLabelPaddingLeft); o.set("systemlabelpaddingright", obj.systemLabelPaddingRight); o.set("accoladebarpaddingright", obj.accoladeBarPaddingRight); + o.set("inlinetuningpaddingright", obj.inlineTuningPaddingRight); o.set("firstnotationstaffpaddingtop", obj.firstNotationStaffPaddingTop); o.set("lastnotationstaffpaddingbottom", obj.lastNotationStaffPaddingBottom); o.set("notationstaffpaddingtop", obj.notationStaffPaddingTop); @@ -109,6 +110,9 @@ export class DisplaySettingsSerializer { case "accoladebarpaddingright": obj.accoladeBarPaddingRight = v! as number; return true; + case "inlinetuningpaddingright": + obj.inlineTuningPaddingRight = v! as number; + return true; case "firstnotationstaffpaddingtop": obj.firstNotationStaffPaddingTop = v! as number; return true; From ae8bc5720c841d7100a20f30a4e96eca5836a5df Mon Sep 17 00:00:00 2001 From: kulek Date: Sun, 21 Jun 2026 09:32:18 +0200 Subject: [PATCH 3/9] test: cover inline tuning rendering --- .../layout/inline-tuning-first-system.png | Bin 0 -> 14366 bytes .../test/visualTests/features/Layout.test.ts | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 packages/alphatab/test-data/visual-tests/layout/inline-tuning-first-system.png diff --git a/packages/alphatab/test-data/visual-tests/layout/inline-tuning-first-system.png b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-first-system.png new file mode 100644 index 0000000000000000000000000000000000000000..66862796f2de4641532c7bbacdb0c1e3152069d3 GIT binary patch literal 14366 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

tj6qeSKPFRiz-H!}gt|rKQKN$0${< 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 M6{E}f7i}K?4*`%Dw*UYD literal 0 HcmV?d00001 diff --git a/packages/alphatab/test/visualTests/features/Layout.test.ts b/packages/alphatab/test/visualTests/features/Layout.test.ts index c2a12bac7..04e7c44af 100644 --- a/packages/alphatab/test/visualTests/features/Layout.test.ts +++ b/packages/alphatab/test/visualTests/features/Layout.test.ts @@ -124,6 +124,24 @@ describe('LayoutTests', () => { }); }); + it('inline-tuning-first-system', async () => { + const settings: Settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\tuningDisplayMode staff + \\track { defaultSystemsLayout 2 } + \\staff { tabs } + 0.6.4 2.6.4 3.6.4 0.5.4 | + 2.5.4 3.5.4 0.4.4 2.4.4 | + 3.4.4 0.3.4 2.3.4 3.3.4 | + 0.2.4 1.2.4 3.2.4 0.1.4 | + `, + 'test-data/visual-tests/layout/inline-tuning-first-system.png', + settings + ); + }); + it('system-layout-tex', async () => { const settings: Settings = new Settings(); settings.display.layoutMode = LayoutMode.Parchment; From fe19a08d8c6d949f209b2e53f00bc89e6a1a513b Mon Sep 17 00:00:00 2001 From: kulek Date: Sun, 21 Jun 2026 19:02:35 +0200 Subject: [PATCH 4/9] refactor: create InlineTuningGlyph --- .../src/rendering/glyphs/InlineTuningGlyph.ts | 72 ++++++++++++ .../src/rendering/staves/StaffSystem.ts | 110 ++++++++---------- 2 files changed, 123 insertions(+), 59 deletions(-) create mode 100644 packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts diff --git a/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts b/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts new file mode 100644 index 000000000..6310e2024 --- /dev/null +++ b/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts @@ -0,0 +1,72 @@ +import { Tuning } from '@coderline/alphatab/model/Tuning'; +import { TrackSubElement } from '@coderline/alphatab/model/Track'; +import { NotationElement } from '@coderline/alphatab/NotationSettings'; +import { type ICanvas, TextAlign, TextBaseline } from '@coderline/alphatab/platform/ICanvas'; +import { TabBarRenderer } from '@coderline/alphatab/rendering/TabBarRenderer'; +import { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; +import type { RenderStaff } from '@coderline/alphatab/rendering/staves/RenderStaff'; +import { ElementStyleHelper } from '@coderline/alphatab/rendering/utils/ElementStyleHelper'; + +/** + * @internal + */ +export class InlineTuningGlyph extends Glyph { + public readonly staff: RenderStaff; + + private readonly _tabRenderer: TabBarRenderer; + private readonly _tunings: number[]; + + public constructor(staff: RenderStaff, tabRenderer: TabBarRenderer) { + super(0, 0); + this.staff = staff; + this._tabRenderer = tabRenderer; + this._tunings = staff.modelStaff.stringTuning.tunings; + this.renderer = tabRenderer; + } + + public override doLayout(): void { + const canvas = this.renderer.scoreRenderer.canvas!; + const oldFont = canvas.font; + canvas.font = this.renderer.resources.elementFonts.get(NotationElement.GuitarTuning)!; + + let textWidth = 0; + for (const tuning of this._tunings) { + textWidth = Math.max(textWidth, canvas.measureText(Tuning.getTextForTuning(tuning, false)).width); + } + + canvas.font = oldFont; + + this.width = textWidth > 0 ? textWidth + this.renderer.settings.display.inlineTuningPaddingRight : 0; + this.height = this._tabRenderer.height; + } + + public override paint(cx: number, cy: number, canvas: ICanvas): void { + if (this.width === 0) { + return; + } + + const oldFont = canvas.font; + const oldBaseLine = canvas.textBaseline; + const oldTextAlign = canvas.textAlign; + + canvas.font = this.renderer.resources.elementFonts.get(NotationElement.GuitarTuning)!; + canvas.textBaseline = TextBaseline.Middle; + canvas.textAlign = TextAlign.Right; + + const textEndX = cx - this.renderer.settings.display.inlineTuningPaddingRight; + + using _ = ElementStyleHelper.track(canvas, TrackSubElement.StringTuning, this.staff.modelStaff.track, true); + + for (let i = 0, j = this._tunings.length; i < j; i++) { + canvas.fillText( + Tuning.getTextForTuning(this._tunings[i], false), + textEndX, + cy + this._tabRenderer.y + this._tabRenderer.getLineY(i) + ); + } + + canvas.font = oldFont; + canvas.textBaseline = oldBaseLine; + canvas.textAlign = oldTextAlign; + } +} diff --git a/packages/alphatab/src/rendering/staves/StaffSystem.ts b/packages/alphatab/src/rendering/staves/StaffSystem.ts index eff3a1c64..81cec2156 100644 --- a/packages/alphatab/src/rendering/staves/StaffSystem.ts +++ b/packages/alphatab/src/rendering/staves/StaffSystem.ts @@ -10,7 +10,6 @@ import { TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import { type Track, TrackSubElement } from '@coderline/alphatab/model/Track'; -import { Tuning } from '@coderline/alphatab/model/Tuning'; import { NotationElement } from '@coderline/alphatab/NotationSettings'; import { CanvasHelper, type ICanvas, TextAlign, TextBaseline } from '@coderline/alphatab/platform/ICanvas'; import type { RenderingResources } from '@coderline/alphatab/RenderingResources'; @@ -18,6 +17,7 @@ import type { BarRendererBase } from '@coderline/alphatab/rendering/BarRendererB import type { LineBarRenderer } from '@coderline/alphatab/rendering/LineBarRenderer'; import type { ScoreLayout } from '@coderline/alphatab/rendering/layout/ScoreLayout'; import { TabBarRenderer } from '@coderline/alphatab/rendering/TabBarRenderer'; +import { InlineTuningGlyph } from '@coderline/alphatab/rendering/glyphs/InlineTuningGlyph'; import { BarLayoutingInfo } from '@coderline/alphatab/rendering/staves/BarLayoutingInfo'; import { MasterBarsRenderers } from '@coderline/alphatab/rendering/staves/MasterBarsRenderers'; import type { RenderStaff } from '@coderline/alphatab/rendering/staves/RenderStaff'; @@ -161,6 +161,8 @@ export class StaffSystem { private _brackets: SystemBracket[] = []; private _staffToBracket = new Map(); + private _inlineTuningGlyphs: InlineTuningGlyph[] = []; + private _inlineTuningWidth = 0; private _contentHeight = 0; private _hasSystemSeparator = false; @@ -660,7 +662,8 @@ export class StaffSystem { } } - this.accoladeWidth += this._getInlineTuningWidth(this.allStaves, this.layout.renderer.canvas!); + this._createInlineTuningGlyphs(); + this.accoladeWidth += this._inlineTuningWidth; // NOTE: we have a chicken-egg problem when it comes to scaling braces which we try to mitigate here: // - The brace scales with the height of the system @@ -701,15 +704,42 @@ export class StaffSystem { } } - private _shouldRenderInlineTuning(staff: RenderStaff): boolean { + private _createInlineTuningGlyphs(): void { + this._inlineTuningGlyphs = []; + this._inlineTuningWidth = 0; + const score = this.layout.renderer.score!; if ( this.index !== 0 || - !staff.isVisible || - staff.staffId !== TabBarRenderer.StaffId || !this.layout.renderer.settings.notation.isNotationElementVisible(NotationElement.GuitarTuning) || !score.stylesheet.globalDisplayTuning || score.stylesheet.tuningDisplayMode !== TuningDisplayMode.Staff + ) { + return; + } + + for (const staff of this.allStaves) { + if (!this._shouldCreateInlineTuningGlyph(staff)) { + continue; + } + + const renderer = staff.barRenderers[0]; + if (!(renderer instanceof TabBarRenderer)) { + continue; + } + + const glyph = new InlineTuningGlyph(staff, renderer); + glyph.doLayout(); + this._inlineTuningGlyphs.push(glyph); + this._inlineTuningWidth = Math.max(this._inlineTuningWidth, glyph.width); + } + } + + private _shouldCreateInlineTuningGlyph(staff: RenderStaff): boolean { + const score = this.layout.renderer.score!; + if ( + !staff.isVisible || + staff.staffId !== TabBarRenderer.StaffId ) { return false; } @@ -732,30 +762,22 @@ export class StaffSystem { ); } - private _getInlineTuningWidth(staves: Iterable, canvas: ICanvas): number { - const oldFont = canvas.font; - canvas.font = this.layout.renderer.settings.display.resources.elementFonts.get(NotationElement.GuitarTuning)!; + private _getInlineTuningWidthForTrackGroup(group: StaffTrackGroup): number { + return this._getInlineTuningWidth(glyph => glyph.staff.staffTrackGroup === group); + } - let maxWidth = 0; - for (const staff of staves) { - if (!this._shouldRenderInlineTuning(staff)) { - continue; - } + private _getInlineTuningWidthForBracket(bracket: SystemBracket): number { + return this._getInlineTuningWidth(glyph => bracket.includesStaff(glyph.staff)); + } - for (const tuning of staff.modelStaff.stringTuning.tunings) { - maxWidth = Math.max(maxWidth, canvas.measureText(Tuning.getTextForTuning(tuning, false)).width); + private _getInlineTuningWidth(includeGlyph: (glyph: InlineTuningGlyph) => boolean): number { + let width = 0; + for (const glyph of this._inlineTuningGlyphs) { + if (includeGlyph(glyph)) { + width = Math.max(width, glyph.width); } } - canvas.font = oldFont; - - return maxWidth > 0 ? maxWidth + this.layout.renderer.settings.display.inlineTuningPaddingRight : 0; - } - - private _getInlineTuningWidthForBracket(bracket: SystemBracket, canvas: ICanvas): number { - return this._getInlineTuningWidth( - this.allStaves.filter(staff => bracket.includesStaff(staff)), - canvas - ); + return width; } private _getStaffTrackGroup(track: Track): StaffTrackGroup | null { @@ -936,7 +958,7 @@ export class StaffSystem { g.staves[0].x - // left side of the bracket settings.display.accoladeBarPaddingRight - - this._getInlineTuningWidth(g.staves, canvas) - + this._getInlineTuningWidthForTrackGroup(g) - (g.bracket?.width ?? 0) - // padding between label and bracket settings.display.systemLabelPaddingRight; @@ -1010,39 +1032,9 @@ export class StaffSystem { } private _paintInlineTunings(cx: number, cy: number, canvas: ICanvas): void { - const oldFont = canvas.font; - const oldBaseLine = canvas.textBaseline; - const oldTextAlign = canvas.textAlign; - - canvas.font = this.layout.renderer.settings.display.resources.elementFonts.get(NotationElement.GuitarTuning)!; - canvas.textBaseline = TextBaseline.Middle; - canvas.textAlign = TextAlign.Right; - - for (const staff of this.allStaves) { - if (!this._shouldRenderInlineTuning(staff)) { - continue; - } - - const renderer = staff.barRenderers[0]; - if (!(renderer instanceof TabBarRenderer)) { - continue; - } - const textEndX = cx + staff.x - this.layout.renderer.settings.display.inlineTuningPaddingRight; - - using _ = ElementStyleHelper.track(canvas, TrackSubElement.StringTuning, staff.modelStaff.track, true); - - for (let i = 0, j = staff.modelStaff.stringTuning.tunings.length; i < j; i++) { - canvas.fillText( - Tuning.getTextForTuning(staff.modelStaff.stringTuning.tunings[i], false), - textEndX, - cy + staff.y + renderer.y + renderer.getLineY(i) - ); - } + for (const glyph of this._inlineTuningGlyphs) { + glyph.paint(cx + glyph.staff.x, cy + glyph.staff.y, canvas); } - - canvas.font = oldFont; - canvas.textBaseline = oldBaseLine; - canvas.textAlign = oldTextAlign; } private _paintBrackets(cx: number, cy: number, canvas: ICanvas) { @@ -1053,7 +1045,7 @@ export class StaffSystem { const barStartX: number = cx + bracket.firstVisibleStaffInBracket!.x; const barSize: number = bracket.width; const barOffset: number = - settings.display.accoladeBarPaddingRight + this._getInlineTuningWidthForBracket(bracket, canvas); + settings.display.accoladeBarPaddingRight + this._getInlineTuningWidthForBracket(bracket); const firstStart: number = cy + bracket.firstVisibleStaffInBracket!.contentTop; const lastEnd: number = cy + bracket.lastVisibleStaffInBracket!.contentBottom; let accoladeStart: number = firstStart; From 181728c414ee961d2dc5708019df7923452e0884 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Thu, 2 Jul 2026 13:48:08 +0200 Subject: [PATCH 5/9] chore: a bit cleanup --- .../src/rendering/glyphs/InlineTuningGlyph.ts | 13 +++++-------- .../alphatab/src/rendering/staves/StaffSystem.ts | 8 ++------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts b/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts index 6310e2024..0c021d440 100644 --- a/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts @@ -1,9 +1,9 @@ -import { Tuning } from '@coderline/alphatab/model/Tuning'; import { TrackSubElement } from '@coderline/alphatab/model/Track'; +import { Tuning } from '@coderline/alphatab/model/Tuning'; import { NotationElement } from '@coderline/alphatab/NotationSettings'; import { type ICanvas, TextAlign, TextBaseline } from '@coderline/alphatab/platform/ICanvas'; -import { TabBarRenderer } from '@coderline/alphatab/rendering/TabBarRenderer'; import { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; +import type { LineBarRenderer } from '@coderline/alphatab/rendering/LineBarRenderer'; import type { RenderStaff } from '@coderline/alphatab/rendering/staves/RenderStaff'; import { ElementStyleHelper } from '@coderline/alphatab/rendering/utils/ElementStyleHelper'; @@ -13,15 +13,12 @@ import { ElementStyleHelper } from '@coderline/alphatab/rendering/utils/ElementS export class InlineTuningGlyph extends Glyph { public readonly staff: RenderStaff; - private readonly _tabRenderer: TabBarRenderer; private readonly _tunings: number[]; - public constructor(staff: RenderStaff, tabRenderer: TabBarRenderer) { + public constructor(staff: RenderStaff) { super(0, 0); this.staff = staff; - this._tabRenderer = tabRenderer; this._tunings = staff.modelStaff.stringTuning.tunings; - this.renderer = tabRenderer; } public override doLayout(): void { @@ -37,7 +34,7 @@ export class InlineTuningGlyph extends Glyph { canvas.font = oldFont; this.width = textWidth > 0 ? textWidth + this.renderer.settings.display.inlineTuningPaddingRight : 0; - this.height = this._tabRenderer.height; + this.height = this.renderer.height; } public override paint(cx: number, cy: number, canvas: ICanvas): void { @@ -61,7 +58,7 @@ export class InlineTuningGlyph extends Glyph { canvas.fillText( Tuning.getTextForTuning(this._tunings[i], false), textEndX, - cy + this._tabRenderer.y + this._tabRenderer.getLineY(i) + cy + this.renderer.y + (this.renderer as LineBarRenderer).getLineY(i) ); } diff --git a/packages/alphatab/src/rendering/staves/StaffSystem.ts b/packages/alphatab/src/rendering/staves/StaffSystem.ts index 81cec2156..47e869525 100644 --- a/packages/alphatab/src/rendering/staves/StaffSystem.ts +++ b/packages/alphatab/src/rendering/staves/StaffSystem.ts @@ -723,12 +723,8 @@ export class StaffSystem { continue; } - const renderer = staff.barRenderers[0]; - if (!(renderer instanceof TabBarRenderer)) { - continue; - } - - const glyph = new InlineTuningGlyph(staff, renderer); + const glyph = new InlineTuningGlyph(staff); + glyph.renderer = staff.barRenderers[0]; glyph.doLayout(); this._inlineTuningGlyphs.push(glyph); this._inlineTuningWidth = Math.max(this._inlineTuningWidth, glyph.width); From 71777522d4ab9b26417dbc6c2497f65a817ba6b2 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Thu, 2 Jul 2026 13:48:18 +0200 Subject: [PATCH 6/9] feat: add guitar pro stylesheet support --- .../alphatab/src/importer/BinaryStylesheet.ts | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/alphatab/src/importer/BinaryStylesheet.ts b/packages/alphatab/src/importer/BinaryStylesheet.ts index 7c19d014b..b0a9b68f0 100644 --- a/packages/alphatab/src/importer/BinaryStylesheet.ts +++ b/packages/alphatab/src/importer/BinaryStylesheet.ts @@ -10,7 +10,8 @@ import { type BracketExtendMode, TrackNameMode, TrackNameOrientation, - TrackNamePolicy + TrackNamePolicy, + TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import type { IWriteable } from '@coderline/alphatab/io/IWriteable'; import { AlphaTabError, AlphaTabErrorType } from '@coderline/alphatab/AlphaTabError'; @@ -87,7 +88,12 @@ export class BinaryStylesheet { const readable: ByteBuffer = ByteBuffer.fromBuffer(data); const entryCount: number = IOHelper.readInt32BE(readable); for (let i: number = 0; i < entryCount; i++) { - const key: string = GpBinaryHelpers.gpReadString(readable, readable.readByte(), 'utf-8', maxDecodingBufferSize); + const key: string = GpBinaryHelpers.gpReadString( + readable, + readable.readByte(), + 'utf-8', + maxDecodingBufferSize + ); const type: DataType = readable.readByte() as DataType; this._types.set(key, type); switch (type) { @@ -104,7 +110,12 @@ export class BinaryStylesheet { this.addValue(key, fvalue); break; case DataType.String: - const s: string = GpBinaryHelpers.gpReadString(readable, IOHelper.readInt16BE(readable), 'utf-8', maxDecodingBufferSize); + const s: string = GpBinaryHelpers.gpReadString( + readable, + IOHelper.readInt16BE(readable), + 'utf-8', + maxDecodingBufferSize + ); this.addValue(key, s); break; case DataType.Point: @@ -362,6 +373,16 @@ export class BinaryStylesheet { break; } break; + case 'Global/TuningDisplayMode': + switch (value as number) { + case 0: + score.stylesheet.tuningDisplayMode = TuningDisplayMode.Staff; + break; + case 2: + score.stylesheet.tuningDisplayMode = TuningDisplayMode.Score; + break; + } + break; } } } @@ -596,6 +617,15 @@ export class BinaryStylesheet { break; } + switch (score.stylesheet.tuningDisplayMode) { + case TuningDisplayMode.Score: + binaryStylesheet.addValue('Global/TuningDisplayMode', 2, DataType.Integer); + break; + case TuningDisplayMode.Staff: + binaryStylesheet.addValue('Global/TuningDisplayMode', 0, DataType.Integer); + break; + } + const writer = ByteBuffer.withCapacity(128); binaryStylesheet.writeTo(writer); return writer.toArray(); From d9e005f51179b5811287f0eae549c370ace9ae45 Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Thu, 2 Jul 2026 14:14:56 +0200 Subject: [PATCH 7/9] fix: show tuning before bracket --- .../src/rendering/staves/StaffSystem.ts | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/alphatab/src/rendering/staves/StaffSystem.ts b/packages/alphatab/src/rendering/staves/StaffSystem.ts index 62db8affc..19835b8bd 100644 --- a/packages/alphatab/src/rendering/staves/StaffSystem.ts +++ b/packages/alphatab/src/rendering/staves/StaffSystem.ts @@ -806,10 +806,7 @@ export class StaffSystem { private _shouldCreateInlineTuningGlyph(staff: RenderStaff): boolean { const score = this.layout.renderer.score!; - if ( - !staff.isVisible || - staff.staffId !== TabBarRenderer.StaffId - ) { + if (!staff.isVisible || staff.staffId !== TabBarRenderer.StaffId) { return false; } @@ -832,17 +829,9 @@ export class StaffSystem { } private _getInlineTuningWidthForTrackGroup(group: StaffTrackGroup): number { - return this._getInlineTuningWidth(glyph => glyph.staff.staffTrackGroup === group); - } - - private _getInlineTuningWidthForBracket(bracket: SystemBracket): number { - return this._getInlineTuningWidth(glyph => bracket.includesStaff(glyph.staff)); - } - - private _getInlineTuningWidth(includeGlyph: (glyph: InlineTuningGlyph) => boolean): number { let width = 0; for (const glyph of this._inlineTuningGlyphs) { - if (includeGlyph(glyph)) { + if (glyph.staff.staffTrackGroup === group) { width = Math.max(width, glyph.width); } } @@ -1101,8 +1090,15 @@ export class StaffSystem { } private _paintInlineTunings(cx: number, cy: number, canvas: ICanvas): void { + const accoladeBarPaddingRight = this.layout.renderer.settings.display.accoladeBarPaddingRight; for (const glyph of this._inlineTuningGlyphs) { - glyph.paint(cx + glyph.staff.x, cy + glyph.staff.y, canvas); + // Place labels between the track name and the bracket: + // shift the glyph's anchor left by the bracket's paint area + // (accolade bar padding + bracket width) so the tuning sits on the + // outside of the bracket rather than inside it. + const bracket = this._staffToBracket.has(glyph.staff) ? this._staffToBracket.get(glyph.staff)! : undefined; + const bracketOffset = bracket && bracket.width > 0 ? accoladeBarPaddingRight + bracket.width : 0; + glyph.paint(cx + glyph.staff.x - bracketOffset, cy + glyph.staff.y, canvas); } } @@ -1113,8 +1109,7 @@ export class StaffSystem { if (bracket.canPaint) { const barStartX: number = cx + bracket.firstVisibleStaffInBracket!.x; const barSize: number = bracket.width; - const barOffset: number = - settings.display.accoladeBarPaddingRight + this._getInlineTuningWidthForBracket(bracket); + const barOffset: number = settings.display.accoladeBarPaddingRight; const firstStart: number = cy + bracket.firstVisibleStaffInBracket!.contentTop; const lastEnd: number = cy + bracket.lastVisibleStaffInBracket!.contentBottom; let accoladeStart: number = firstStart; From 124dd4b50cf5d336f6c5e09ce27cfaa86dab725e Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Thu, 2 Jul 2026 14:15:03 +0200 Subject: [PATCH 8/9] test: extend test matrix --- .../layout/inline-tuning-first-system.png | Bin 14366 -> 14317 bytes .../layout/inline-tuning-per-track-hidden.png | Bin 0 -> 13429 bytes .../layout/inline-tuning-seven-string.png | Bin 0 -> 7978 bytes .../layout/inline-tuning-with-bracket.png | Bin 0 -> 16753 bytes .../test/visualTests/features/Layout.test.ts | 59 ++++++++++++++++++ 5 files changed, 59 insertions(+) create mode 100644 packages/alphatab/test-data/visual-tests/layout/inline-tuning-per-track-hidden.png create mode 100644 packages/alphatab/test-data/visual-tests/layout/inline-tuning-seven-string.png create mode 100644 packages/alphatab/test-data/visual-tests/layout/inline-tuning-with-bracket.png diff --git a/packages/alphatab/test-data/visual-tests/layout/inline-tuning-first-system.png b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-first-system.png index 66862796f2de4641532c7bbacdb0c1e3152069d3..87029d782092eae142e127e10d432b4306ede7ff 100644 GIT binary patch literal 14317 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 literal 14366 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

tj6qeSKPFRiz-H!}gt|rKQKN$0${< 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 M6{E}f7i}K?4*`%Dw*UYD diff --git a/packages/alphatab/test-data/visual-tests/layout/inline-tuning-per-track-hidden.png b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-per-track-hidden.png new file mode 100644 index 0000000000000000000000000000000000000000..4b51f644a65fe40e9631952a8aacaf69172b3b1d GIT binary patch literal 13429 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 Nc+TuB>5S{G{{;h&JKX>P literal 0 HcmV?d00001 diff --git a/packages/alphatab/test-data/visual-tests/layout/inline-tuning-seven-string.png b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-seven-string.png new file mode 100644 index 0000000000000000000000000000000000000000..bab99aee739d6a7327fe37fff952d674c8992ed2 GIT binary patch literal 7978 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 literal 0 HcmV?d00001 diff --git a/packages/alphatab/test/visualTests/features/Layout.test.ts b/packages/alphatab/test/visualTests/features/Layout.test.ts index 0f7dfeec8..4648b5920 100644 --- a/packages/alphatab/test/visualTests/features/Layout.test.ts +++ b/packages/alphatab/test/visualTests/features/Layout.test.ts @@ -142,6 +142,65 @@ describe('LayoutTests', () => { ); }); + it('inline-tuning-with-bracket', async () => { + const settings: Settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\tuningDisplayMode staff + \\bracketExtendMode groupsimilarinstruments + \\track "Guitar 1" + \\staff { tabs } + 0.6.4 2.6.4 3.6.4 0.5.4 | + \\track "Guitar 2" + \\staff { tabs } + 0.6.4 2.6.4 3.6.4 0.5.4 | + `, + 'test-data/visual-tests/layout/inline-tuning-with-bracket.png', + settings, + o => { + o.tracks = [0, 1]; + } + ); + }); + + it('inline-tuning-seven-string', async () => { + const settings: Settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\tuningDisplayMode staff + \\tuning E4 B3 G3 D3 A2 E2 B1 + \\staff { tabs } + 0.7.4 2.7.4 3.7.4 0.6.4 | + `, + 'test-data/visual-tests/layout/inline-tuning-seven-string.png', + settings + ); + }); + + it('inline-tuning-per-track-hidden', async () => { + const settings: Settings = new Settings(); + settings.display.layoutMode = LayoutMode.Parchment; + await VisualTestHelper.runVisualTestTex( + ` + \\tuningDisplayMode staff + \\track "Guitar" + \\staff { tabs } + 0.6.4 2.6.4 3.6.4 0.5.4 | + \\track "Bass" + \\staff { tabs } + \\tuning E2 A1 D2 G2 hide + 0.4.4 2.4.4 3.4.4 0.3.4 | + `, + 'test-data/visual-tests/layout/inline-tuning-per-track-hidden.png', + settings, + o => { + o.tracks = [0, 1]; + } + ); + }); + it('system-layout-tex', async () => { const settings: Settings = new Settings(); settings.display.layoutMode = LayoutMode.Parchment; From 5ff6a20784536af7a513383dfada40f8773f90ce Mon Sep 17 00:00:00 2001 From: Danielku15 Date: Thu, 2 Jul 2026 14:18:07 +0200 Subject: [PATCH 9/9] test: add gp8 import test --- .../test-data/guitarpro8/tuning-before-staff.gp | Bin 0 -> 12012 bytes .../alphatab/test/importer/Gp8Importer.test.ts | 11 ++++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 packages/alphatab/test-data/guitarpro8/tuning-before-staff.gp diff --git a/packages/alphatab/test-data/guitarpro8/tuning-before-staff.gp b/packages/alphatab/test-data/guitarpro8/tuning-before-staff.gp new file mode 100644 index 0000000000000000000000000000000000000000..4028c8bcf8614e75bd8a61783bd4162592216640 GIT binary patch literal 12012 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 { expect(score.masterBars.length).toBe(100); expect(score.tracks.length).toBe(3); }); + + it('tuning-before-staff', async () =>{ + const reader = await prepareImporterWithFile('guitarpro8/tuning-before-staff.gp'); + const score = reader.readScore(); + + expect(score.stylesheet.tuningDisplayMode).toBe(TuningDisplayMode.Staff); + + }) });