diff --git a/packages/alphatab/src/DisplaySettings.ts b/packages/alphatab/src/DisplaySettings.ts index 3c5a8e5f5..0782d660f 100644 --- a/packages/alphatab/src/DisplaySettings.ts +++ b/packages/alphatab/src/DisplaySettings.ts @@ -323,6 +323,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 d0e827cdb..99aedb8f0 100644 --- a/packages/alphatab/src/generated/DisplaySettingsJson.ts +++ b/packages/alphatab/src/generated/DisplaySettingsJson.ts @@ -286,6 +286,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 3c821bbb2..03ed85be3 100644 --- a/packages/alphatab/src/generated/DisplaySettingsSerializer.ts +++ b/packages/alphatab/src/generated/DisplaySettingsSerializer.ts @@ -43,6 +43,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); @@ -116,6 +117,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; diff --git a/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts b/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts index a3595739a..7c80a34b6 100644 --- a/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts +++ b/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts @@ -10,6 +10,7 @@ import { TabStaffConfigSerializer } from "@coderline/alphatab/generated/model/Ta import { SlashStaffConfigSerializer } from "@coderline/alphatab/generated/model/SlashStaffConfigSerializer"; import { NumberedStaffConfigSerializer } from "@coderline/alphatab/generated/model/NumberedStaffConfigSerializer"; 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"; @@ -33,6 +34,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); @@ -88,6 +90,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/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(); 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 c3db411d2..cbda1438a 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 408a5060b..83ff9ad8d 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; @@ -2579,6 +2591,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 f5a74ca01..c579594a4 100644 --- a/packages/alphatab/src/model/RenderStylesheet.ts +++ b/packages/alphatab/src/model/RenderStylesheet.ts @@ -80,6 +80,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 @@ -127,6 +143,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 4a450a87d..5edccbd36 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/glyphs/InlineTuningGlyph.ts b/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts new file mode 100644 index 000000000..0c021d440 --- /dev/null +++ b/packages/alphatab/src/rendering/glyphs/InlineTuningGlyph.ts @@ -0,0 +1,69 @@ +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 { 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'; + +/** + * @internal + */ +export class InlineTuningGlyph extends Glyph { + public readonly staff: RenderStaff; + + private readonly _tunings: number[]; + + public constructor(staff: RenderStaff) { + super(0, 0); + this.staff = staff; + this._tunings = staff.modelStaff.stringTuning.tunings; + } + + 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.renderer.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.renderer.y + (this.renderer as LineBarRenderer).getLineY(i) + ); + } + + canvas.font = oldFont; + canvas.textBaseline = oldBaseLine; + canvas.textAlign = oldTextAlign; + } +} diff --git a/packages/alphatab/src/rendering/layout/ScoreLayout.ts b/packages/alphatab/src/rendering/layout/ScoreLayout.ts index 4b616f26f..c469aab71 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'; @@ -317,7 +318,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 6bd00f03f..19835b8bd 100644 --- a/packages/alphatab/src/rendering/staves/StaffSystem.ts +++ b/packages/alphatab/src/rendering/staves/StaffSystem.ts @@ -6,7 +6,8 @@ import { BracketExtendMode, TrackNameMode, TrackNameOrientation, - TrackNamePolicy + TrackNamePolicy, + TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import { SimileMark } from '@coderline/alphatab/model/SimileMark'; import { type Track, TrackSubElement } from '@coderline/alphatab/model/Track'; @@ -17,6 +18,8 @@ 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 { 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'; @@ -182,6 +185,8 @@ export class StaffSystem { private _brackets: SystemBracket[] = []; private _staffToBracket = new Map(); + private _inlineTuningGlyphs: InlineTuningGlyph[] = []; + private _inlineTuningWidth = 0; private _contentHeight = 0; private _hasSystemSeparator = false; @@ -722,6 +727,9 @@ export class StaffSystem { } } + this._createInlineTuningGlyphs(); + this.accoladeWidth += this._inlineTuningWidth; + let currentY: number = 0; for (const staff of this.allStaves) { staff.y = currentY; @@ -769,6 +777,67 @@ export class StaffSystem { return fingerprint; } + private _createInlineTuningGlyphs(): void { + this._inlineTuningGlyphs = []; + this._inlineTuningWidth = 0; + + const score = this.layout.renderer.score!; + if ( + this.index !== 0 || + !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 glyph = new InlineTuningGlyph(staff); + glyph.renderer = staff.barRenderers[0]; + 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; + } + + 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 _getInlineTuningWidthForTrackGroup(group: StaffTrackGroup): number { + let width = 0; + for (const glyph of this._inlineTuningGlyphs) { + if (glyph.staff.staffTrackGroup === group) { + width = Math.max(width, glyph.width); + } + } + return width; + } + 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]; @@ -947,6 +1016,7 @@ export class StaffSystem { g.staves[0].x - // left side of the bracket settings.display.accoladeBarPaddingRight - + this._getInlineTuningWidthForTrackGroup(g) - (g.bracket?.width ?? 0) - // padding between label and bracket settings.display.systemLabelPaddingRight; @@ -982,6 +1052,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; @@ -1017,6 +1089,19 @@ 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) { + // 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); + } + } + private _paintBrackets(cx: number, cy: number, canvas: ICanvas) { const settings = this.layout.renderer.settings; 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 000000000..4028c8bcf Binary files /dev/null and b/packages/alphatab/test-data/guitarpro8/tuning-before-staff.gp differ 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 000000000..87029d782 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-first-system.png differ 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 000000000..4b51f644a Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-per-track-hidden.png differ 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 000000000..bab99aee7 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-seven-string.png differ diff --git a/packages/alphatab/test-data/visual-tests/layout/inline-tuning-with-bracket.png b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-with-bracket.png new file mode 100644 index 000000000..c65c20cd5 Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/layout/inline-tuning-with-bracket.png differ 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/alphatab/test/importer/Gp8Importer.test.ts b/packages/alphatab/test/importer/Gp8Importer.test.ts index 15b8d7c0f..041dfb9c8 100644 --- a/packages/alphatab/test/importer/Gp8Importer.test.ts +++ b/packages/alphatab/test/importer/Gp8Importer.test.ts @@ -10,7 +10,8 @@ import { BracketExtendMode, TrackNameMode, TrackNameOrientation, - TrackNamePolicy + TrackNamePolicy, + TuningDisplayMode } from '@coderline/alphatab/model/RenderStylesheet'; import { ScoreSubElement } from '@coderline/alphatab/model/Score'; import { TextAlign } from '@coderline/alphatab/platform/ICanvas'; @@ -516,4 +517,12 @@ describe('Gp8ImporterTest', () => { 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); + + }) }); diff --git a/packages/alphatab/test/visualTests/features/Layout.test.ts b/packages/alphatab/test/visualTests/features/Layout.test.ts index 9b20f84b3..4648b5920 100644 --- a/packages/alphatab/test/visualTests/features/Layout.test.ts +++ b/packages/alphatab/test/visualTests/features/Layout.test.ts @@ -124,6 +124,83 @@ 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('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; 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 + ` +};