diff --git a/packages/alphatab/src/generated/model/BarSerializer.ts b/packages/alphatab/src/generated/model/BarSerializer.ts index d1e4cbd5d..fc757c6df 100644 --- a/packages/alphatab/src/generated/model/BarSerializer.ts +++ b/packages/alphatab/src/generated/model/BarSerializer.ts @@ -16,6 +16,7 @@ import { SustainPedalMarker } from "@coderline/alphatab/model/Bar"; import { BarLineStyle } from "@coderline/alphatab/model/Bar"; import { KeySignature } from "@coderline/alphatab/model/KeySignature"; import { KeySignatureType } from "@coderline/alphatab/model/KeySignatureType"; +import { BarNumberDisplay } from "@coderline/alphatab/model/RenderStylesheet"; import { BarStyle } from "@coderline/alphatab/model/Bar"; /** * @internal @@ -44,6 +45,7 @@ export class BarSerializer { o.set("barlineright", obj.barLineRight as number); o.set("keysignature", obj.keySignature as number); o.set("keysignaturetype", obj.keySignatureType as number); + o.set("barnumberdisplay", obj.barNumberDisplay as number | undefined); if (obj.style) { o.set("style", BarStyleSerializer.toJson(obj.style)); } @@ -97,6 +99,9 @@ export class BarSerializer { case "keysignaturetype": obj.keySignatureType = JsonHelper.parseEnum(v, KeySignatureType)!; return true; + case "barnumberdisplay": + obj.barNumberDisplay = JsonHelper.parseEnum(v, BarNumberDisplay); + return true; case "style": if (v) { obj.style = new BarStyle(); diff --git a/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts b/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts index dc5fe4d92..10ac9a9ac 100644 --- a/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts +++ b/packages/alphatab/src/generated/model/RenderStylesheetSerializer.ts @@ -9,6 +9,7 @@ import { BracketExtendMode } 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"; +import { BarNumberDisplay } from "@coderline/alphatab/model/RenderStylesheet"; /** * @internal */ @@ -62,6 +63,7 @@ export class RenderStylesheetSerializer { o.set("hideemptystaves", obj.hideEmptyStaves); o.set("hideemptystavesinfirstsystem", obj.hideEmptyStavesInFirstSystem); o.set("showsinglestaffbrackets", obj.showSingleStaffBrackets); + o.set("barnumberdisplay", obj.barNumberDisplay as number); return o; } public static setProperty(obj: RenderStylesheet, property: string, v: unknown): boolean { @@ -132,6 +134,9 @@ export class RenderStylesheetSerializer { case "showsinglestaffbrackets": obj.showSingleStaffBrackets = v! as boolean; return true; + case "barnumberdisplay": + obj.barNumberDisplay = JsonHelper.parseEnum(v, BarNumberDisplay)!; + return true; } return false; } diff --git a/packages/alphatab/src/importer/BinaryStylesheet.ts b/packages/alphatab/src/importer/BinaryStylesheet.ts index ace358467..3da315306 100644 --- a/packages/alphatab/src/importer/BinaryStylesheet.ts +++ b/packages/alphatab/src/importer/BinaryStylesheet.ts @@ -6,6 +6,7 @@ import { BendPoint } from '@coderline/alphatab/model/BendPoint'; import { Bounds } from '@coderline/alphatab/rendering/utils/Bounds'; import { Color } from '@coderline/alphatab/model/Color'; import { + BarNumberDisplay, type BracketExtendMode, TrackNameMode, TrackNameOrientation, @@ -347,6 +348,20 @@ export class BinaryStylesheet { ModelUtils.getOrCreateHeaderFooterStyle(score, ScoreSubElement.CopyrightSecondLine).isVisible = value as boolean; break; + + case 'System/barIndexDrawType': + switch (value as number) { + case 0: + score.stylesheet.barNumberDisplay = BarNumberDisplay.AllBars; + break; + case 1: + score.stylesheet.barNumberDisplay = BarNumberDisplay.FirstOfSystem; + break; + case 2: + score.stylesheet.barNumberDisplay = BarNumberDisplay.Hide; + break; + } + break; } } } @@ -569,6 +584,18 @@ export class BinaryStylesheet { } } + switch (score.stylesheet.barNumberDisplay) { + case BarNumberDisplay.AllBars: + binaryStylesheet.addValue('System/barIndexDrawType', 0, DataType.Integer); + break; + case BarNumberDisplay.FirstOfSystem: + binaryStylesheet.addValue('System/barIndexDrawType', 1, DataType.Integer); + break; + case BarNumberDisplay.Hide: + binaryStylesheet.addValue('System/barIndexDrawType', 2, DataType.Integer); + break; + } + const writer = ByteBuffer.withCapacity(128); binaryStylesheet.writeTo(writer); return writer.toArray(); diff --git a/packages/alphatab/src/importer/MusicXmlImporter.ts b/packages/alphatab/src/importer/MusicXmlImporter.ts index 41527e328..400647a18 100644 --- a/packages/alphatab/src/importer/MusicXmlImporter.ts +++ b/packages/alphatab/src/importer/MusicXmlImporter.ts @@ -31,6 +31,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 } from '@coderline/alphatab/model/RenderStylesheet'; import { Score } from '@coderline/alphatab/model/Score'; import { Section } from '@coderline/alphatab/model/Section'; import { SimileMark } from '@coderline/alphatab/model/SimileMark'; @@ -198,6 +199,9 @@ export class MusicXmlImporter extends ScoreImporter { private _indexToTrackInfo: Map = new Map(); private _staffToContext: Map = new Map(); + private _currentBarNumberDisplayPart?: BarNumberDisplay; + private _currentBarNumberDisplayBar?: BarNumberDisplay; + private _divisionsPerQuarterNote: number = 1; private _currentDynamics = DynamicValue.F; @@ -868,23 +872,34 @@ export class MusicXmlImporter extends ScoreImporter { break; } } + + this._currentBarNumberDisplayPart = undefined; } private _parsePartwiseMeasure(element: XmlNode, track: Track, index: number) { const masterBar = this._getOrCreateMasterBar(element, index); - this._parsePartMeasure(element, masterBar, track); + const implicit = element.attributes.get('implicit') === 'yes'; + this._parsePartMeasure(element, masterBar, track, implicit, true); + this._currentBarNumberDisplayBar = undefined; } private _parseTimewiseMeasure(element: XmlNode, index: number) { const masterBar = this._getOrCreateMasterBar(element, index); + const implicit = element.attributes.get('implicit') === 'yes'; for (const c of element.childElements()) { switch (c.localName) { case 'part': - this._parseTimewisePart(c, masterBar); + this._parseTimewisePart(c, masterBar, implicit); + this._currentBarNumberDisplayPart = undefined; + break; + case 'print': + this._parsePrint(c, masterBar, undefined, true); break; } } + + this._currentBarNumberDisplayBar = undefined; } private _getOrCreateMasterBar(element: XmlNode, index: number) { @@ -906,14 +921,14 @@ export class MusicXmlImporter extends ScoreImporter { return masterBar; } - private _parseTimewisePart(element: XmlNode, masterBar: MasterBar) { + private _parseTimewisePart(element: XmlNode, masterBar: MasterBar, implicit: boolean) { const id = element.attributes.get('id'); if (!id || !this._idToTrackInfo.has(id)) { return; } const track = this._idToTrackInfo.get(id)!.track; - this._parsePartMeasure(element, masterBar, track); + this._parsePartMeasure(element, masterBar, track, implicit, false); } // current measure state @@ -929,7 +944,13 @@ export class MusicXmlImporter extends ScoreImporter { */ private _lastBeat: Beat | null = null; - private _parsePartMeasure(element: XmlNode, masterBar: MasterBar, track: Track) { + private _parsePartMeasure( + element: XmlNode, + masterBar: MasterBar, + track: Track, + implicit: boolean, + isPartwise: boolean + ) { this._musicalPosition = 0; this._lastBeat = null; @@ -959,7 +980,7 @@ export class MusicXmlImporter extends ScoreImporter { break; // case 'figured-bass': Not supported case 'print': - this._parsePrint(c, masterBar, track); + this._parsePrint(c, masterBar, track, true); break; case 'sound': this._parseSound(c, masterBar, track); @@ -983,17 +1004,52 @@ export class MusicXmlImporter extends ScoreImporter { // initial empty staff and voice (if no other elements created something already) const staff = this._getOrCreateStaff(track, 0); - this._getOrCreateBar(staff, masterBar); + const bar = this._getOrCreateBar(staff, masterBar); + + if (implicit) { + bar.barNumberDisplay = BarNumberDisplay.Hide; + } else if (isPartwise) { + bar.barNumberDisplay = this._currentBarNumberDisplayBar ?? this._currentBarNumberDisplayPart; + } else { + bar.barNumberDisplay = this._currentBarNumberDisplayPart ?? this._currentBarNumberDisplayBar; + } // clear measure attribute this._keyAllStaves = null; } - private _parsePrint(element: XmlNode, masterBar: MasterBar, track: Track) { - if (element.getAttribute('new-system', 'no') === 'yes') { - track.addLineBreaks(masterBar.index); - } else if (element.getAttribute('new-page', 'no') === 'yes') { - track.addLineBreaks(masterBar.index); + private _parsePrint(element: XmlNode, masterBar: MasterBar, track: Track | undefined, isMeasurePrint: boolean) { + if (track !== undefined) { + if (element.getAttribute('new-system', 'no') === 'yes') { + track.addLineBreaks(masterBar.index); + } else if (element.getAttribute('new-page', 'no') === 'yes') { + track.addLineBreaks(masterBar.index); + } + } + + let newDisplay: BarNumberDisplay | undefined = undefined; + for (const c of element.childElements()) { + switch (c.localName) { + case 'measure-numbering': + switch (c.innerText) { + case 'none': + newDisplay = BarNumberDisplay.Hide; + break; + case 'measure': + newDisplay = BarNumberDisplay.AllBars; + break; + case 'system': + newDisplay = BarNumberDisplay.FirstOfSystem; + break; + } + break; + } + } + + if (isMeasurePrint) { + this._currentBarNumberDisplayBar = newDisplay; + } else { + this._currentBarNumberDisplayPart = newDisplay; } } diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts index e9913a616..598cad9ef 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1EnumMappings.ts @@ -14,6 +14,7 @@ import type { NoteAccidentalMode } from '@coderline/alphatab/model/NoteAccidenta import type { Ottavia } from '@coderline/alphatab/model/Ottavia'; import type { Rasgueado } from '@coderline/alphatab/model/Rasgueado'; import type { + BarNumberDisplay, BracketExtendMode, TrackNameMode, TrackNameOrientation, @@ -405,6 +406,14 @@ export class AlphaTex1EnumMappings { public static readonly tremoloPickingStyleReversed = AlphaTex1EnumMappings._reverse( AlphaTex1EnumMappings.tremoloPickingStyle ); + public static readonly barNumberDisplay = new Map([ + ['allbars', 0], + ['firstofsystem', 1], + ['hide', 2] + ]); + public static readonly barNumberDisplayReversed = AlphaTex1EnumMappings._reverse( + AlphaTex1EnumMappings.barNumberDisplay + ); public static readonly keySignaturesMinorReversed = new Map([ [-7, 'abminor'], [-6, 'ebminor'], diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts index 4224a75fc..5a97c2208 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts @@ -193,7 +193,8 @@ export class AlphaTex1LanguageDefinitions { ['chorddiagramsinscore', [[[[10], 1, ['true', 'false']]]]], ['hideemptystaves', null], ['hideemptystavesinfirstsystem', null], - ['showsinglestaffbrackets', null] + ['showsinglestaffbrackets', null], + ['defaultbarnumberdisplay', [[[[10, 17], 0, ['allbars', 'firstofsystem', 'hide']]]]] ]); public static readonly staffMetaDataSignatures = AlphaTex1LanguageDefinitions._signatures([ ['tuning', [[[[10, 17], 0, ['piano', 'none', 'voice']]], [[[10, 17], 5]]]], @@ -478,7 +479,8 @@ export class AlphaTex1LanguageDefinitions { ['sph', [[[[16], 2]]]], ['spu', [[[[16], 2]]]], ['db', null], - ['voicemode', [[[[10, 17], 0, ['staffwise', 'barwise']]]]] + ['voicemode', [[[[10, 17], 0, ['staffwise', 'barwise']]]]], + ['barnumberdisplay', [[[[10, 17], 0, ['allbars', 'firstofsystem', 'hide']]]]] ]); public static readonly metaDataProperties = AlphaTex1LanguageDefinitions._metaProps([ [ @@ -536,6 +538,7 @@ export class AlphaTex1LanguageDefinitions { ['hideemptystaves', null], ['hideemptystavesinfirstsystem', null], ['showsinglestaffbrackets', null], + ['defaultbarnumberdisplay', null], [ 'tuning', [ @@ -592,7 +595,8 @@ export class AlphaTex1LanguageDefinitions { ['sph', null], ['spu', null], ['db', null], - ['voicemode', null] + ['voicemode', null], + ['barnumberdisplay', null] ]); public static readonly metaDataSignatures = [ AlphaTex1LanguageDefinitions.scoreMetaDataSignatures, diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts index cac181aff..4ca9dd5f8 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts @@ -70,7 +70,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 type { RenderStylesheet } from '@coderline/alphatab/model/RenderStylesheet'; +import { BarNumberDisplay, type RenderStylesheet } 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'; @@ -274,6 +274,18 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler case 'showsinglestaffbrackets': score.stylesheet.showSingleStaffBrackets = true; return ApplyNodeResult.Applied; + case 'defaultbarnumberdisplay': + const barNumberDisplay = AlphaTex1LanguageHandler._parseEnumValue( + importer, + metaData.arguments!, + 'bar number display', + AlphaTex1EnumMappings.barNumberDisplay + ); + if (barNumberDisplay === undefined) { + return ApplyNodeResult.NotAppliedSemanticError; + } + score.stylesheet.barNumberDisplay = barNumberDisplay!; + return ApplyNodeResult.Applied; default: return ApplyNodeResult.NotAppliedUnrecognizedMarker; @@ -855,6 +867,18 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler bar.masterBar.isDoubleBar = true; bar.barLineRight = BarLineStyle.LightLight; return ApplyNodeResult.Applied; + case 'barnumberdisplay': + const barNumberDisplay = AlphaTex1LanguageHandler._parseEnumValue( + importer, + metaData.arguments!, + 'bar number display', + AlphaTex1EnumMappings.barNumberDisplay + ); + if (barNumberDisplay === undefined) { + return ApplyNodeResult.NotAppliedSemanticError; + } + bar.barNumberDisplay = barNumberDisplay!; + return ApplyNodeResult.Applied; default: return ApplyNodeResult.NotAppliedUnrecognizedMarker; } @@ -2545,6 +2569,10 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler nodes.push(Atnf.meta('showSingleStaffBrackets')); } + if (stylesheet.barNumberDisplay !== BarNumberDisplay.AllBars) { + nodes.push(Atnf.identMeta('defaultBarNumberDisplay', BarNumberDisplay[stylesheet.barNumberDisplay])); + } + // Unsupported: // 'globaldisplaychorddiagramsontop', // 'pertrackchorddiagramsontop', @@ -2724,6 +2752,10 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler ]; } + if (bar.barNumberDisplay !== undefined) { + nodes.push(Atnf.identMeta('barNumberDisplay', BarNumberDisplay[bar.barNumberDisplay])); + } + return nodes; } private static _buildStaffMetaDataNodes(nodes: AlphaTexMetaDataNode[], staff: Staff) { diff --git a/packages/alphatab/src/model/Bar.ts b/packages/alphatab/src/model/Bar.ts index 4da5dc818..dbb987c47 100644 --- a/packages/alphatab/src/model/Bar.ts +++ b/packages/alphatab/src/model/Bar.ts @@ -8,6 +8,7 @@ import type { Settings } from '@coderline/alphatab/Settings'; import { ElementStyle } from '@coderline/alphatab/model/ElementStyle'; import { KeySignature } from '@coderline/alphatab/model/KeySignature'; import { KeySignatureType } from '@coderline/alphatab/model/KeySignatureType'; +import type { BarNumberDisplay } from '@coderline/alphatab/model/RenderStylesheet'; /** * The different pedal marker types. @@ -396,6 +397,12 @@ export class Bar { */ public keySignatureType: KeySignatureType = KeySignatureType.Major; + /** + * How bar numbers should be displayed. + * If specified, overrides the value from the stylesheet on score level. + */ + public barNumberDisplay?: BarNumberDisplay; + /** * The bar line to draw on the left side of the bar with an "automatic" type resolved to the actual one. * @param isFirstOfSystem Whether the bar is the first one in the system. @@ -490,7 +497,7 @@ export class Bar { if (this.previousBar && this.previousBar.sustainPedals.length > 0) { previousMarker = this.previousBar.sustainPedals[this.previousBar.sustainPedals.length - 1]; - if(previousMarker.pedalType === SustainPedalMarkerType.Up) { + if (previousMarker.pedalType === SustainPedalMarkerType.Up) { previousMarker = null; } } diff --git a/packages/alphatab/src/model/RenderStylesheet.ts b/packages/alphatab/src/model/RenderStylesheet.ts index 689d596c6..81e4a4ad5 100644 --- a/packages/alphatab/src/model/RenderStylesheet.ts +++ b/packages/alphatab/src/model/RenderStylesheet.ts @@ -72,6 +72,25 @@ export enum TrackNameOrientation { Vertical = 1 } +/** + * How bar numbers are displayed + * @public + */ +export enum BarNumberDisplay { + /** + * Show bar numbers on all bars. + */ + AllBars = 0, + /** + * Show bar numbers on the first bar of every system. + */ + FirstOfSystem = 1, + /** + * Hide all bar numbers + */ + Hide = 2 +} + /** * This class represents the rendering stylesheet. * It contains settings which control the display of the score when rendered. @@ -168,14 +187,14 @@ export class RenderStylesheet { /** * Whether to hide empty staves. */ - public hideEmptyStaves: boolean = false; + public hideEmptyStaves: boolean = false; /** * Whether to also hide empty staves in the first system. * @remarks * Only has an effect when activating {@link hideEmptyStaves}. */ - public hideEmptyStavesInFirstSystem :boolean = false; + public hideEmptyStavesInFirstSystem: boolean = false; /** * Whether to show brackets and braces across single staves. @@ -184,4 +203,9 @@ export class RenderStylesheet { * {@link hideEmptyStaves} */ public showSingleStaffBrackets: boolean = false; + + /** + * How bar numbers should be displayed. + */ + public barNumberDisplay: BarNumberDisplay = BarNumberDisplay.AllBars; } diff --git a/packages/alphatab/src/model/_barrel.ts b/packages/alphatab/src/model/_barrel.ts index f6c224c2f..c59399737 100644 --- a/packages/alphatab/src/model/_barrel.ts +++ b/packages/alphatab/src/model/_barrel.ts @@ -1,7 +1,14 @@ export { AccentuationType } from '@coderline/alphatab/model/AccentuationType'; export { AccidentalType } from '@coderline/alphatab/model/AccidentalType'; export { AutomationType, Automation, SyncPointData, type FlatSyncPoint } from '@coderline/alphatab/model/Automation'; -export { Bar, SustainPedalMarkerType, SustainPedalMarker, BarSubElement, BarStyle, BarLineStyle } from '@coderline/alphatab/model/Bar'; +export { + Bar, + SustainPedalMarkerType, + SustainPedalMarker, + BarSubElement, + BarStyle, + BarLineStyle +} from '@coderline/alphatab/model/Bar'; export { BarreShape } from '@coderline/alphatab/model/BarreShape'; export { Beat, BeatBeamingMode, BeatSubElement, BeatStyle } from '@coderline/alphatab/model/Beat'; export { TremoloPickingEffect, TremoloPickingStyle } from '@coderline/alphatab/model/TremoloPickingEffect'; @@ -43,7 +50,8 @@ export { BracketExtendMode, TrackNamePolicy, TrackNameMode, - TrackNameOrientation + TrackNameOrientation, + BarNumberDisplay } from '@coderline/alphatab/model/RenderStylesheet'; export { RepeatGroup } from '@coderline/alphatab/model/RepeatGroup'; export { Score, ScoreSubElement, ScoreStyle, HeaderFooterStyle } from '@coderline/alphatab/model/Score'; diff --git a/packages/alphatab/src/rendering/LineBarRenderer.ts b/packages/alphatab/src/rendering/LineBarRenderer.ts index 4f1b1baf6..6b1195e17 100644 --- a/packages/alphatab/src/rendering/LineBarRenderer.ts +++ b/packages/alphatab/src/rendering/LineBarRenderer.ts @@ -6,6 +6,7 @@ import { GraceType } from '@coderline/alphatab/model/GraceType'; import { ModelUtils } from '@coderline/alphatab/model/ModelUtils'; import { MusicFontSymbol } from '@coderline/alphatab/model/MusicFontSymbol'; import type { Note } from '@coderline/alphatab/model/Note'; +import { BarNumberDisplay } from '@coderline/alphatab/model/RenderStylesheet'; import type { TupletGroup } from '@coderline/alphatab/model/TupletGroup'; import { NotationElement, NotationMode } from '@coderline/alphatab/NotationSettings'; import { CanvasHelper, type ICanvas, TextAlign, TextBaseline } from '@coderline/alphatab/platform/ICanvas'; @@ -172,14 +173,15 @@ export abstract class LineBarRenderer extends BarRendererBase { // override in subclasses } - protected createStartSpacing(): void { + protected createStartSpacing(): boolean { if (this._startSpacing) { - return; + return false; } const padding = this.index === 0 ? this.settings.display.firstStaffPaddingLeft : this.settings.display.staffPaddingLeft; this.addPreBeatGlyph(new SpacingGlyph(0, 0, padding)); this._startSpacing = true; + return true; } protected paintTuplets( @@ -619,14 +621,39 @@ export abstract class LineBarRenderer extends BarRendererBase { super.createPreBeatGlyphs(); this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines)); this.createLinePreBeatGlyphs(); + let hasSpaceAfterStartGlyphs = false; if (this.index === 0) { - this.createStartSpacing(); + hasSpaceAfterStartGlyphs = this.createStartSpacing(); } - if (this.settings.notation.isNotationElementVisible(NotationElement.BarNumber)) { + + if (this.shouldCreateBarNumber()) { this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1)); + } else if (!hasSpaceAfterStartGlyphs) { + this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace)); } } + public shouldCreateBarNumber(): boolean { + let display = BarNumberDisplay.AllBars; + if (!this.settings.notation.isNotationElementVisible(NotationElement.BarNumber)) { + display = BarNumberDisplay.Hide; + } else if (this.bar.barNumberDisplay !== undefined) { + display = this.bar.barNumberDisplay!; + } else { + display = this.bar.staff.track.score.stylesheet.barNumberDisplay; + } + + switch (display) { + case BarNumberDisplay.AllBars: + return true; + case BarNumberDisplay.FirstOfSystem: + return this.isFirstOfStaff; + case BarNumberDisplay.Hide: + return false; + } + return true; + } + protected abstract createLinePreBeatGlyphs(): void; protected override createPostBeatGlyphs(): void { diff --git a/packages/alphatab/src/rendering/NumberedBarRenderer.ts b/packages/alphatab/src/rendering/NumberedBarRenderer.ts index 174048b8c..23dc8a93d 100644 --- a/packages/alphatab/src/rendering/NumberedBarRenderer.ts +++ b/packages/alphatab/src/rendering/NumberedBarRenderer.ts @@ -8,7 +8,6 @@ import { ModelUtils } from '@coderline/alphatab/model/ModelUtils'; import { MusicFontSymbol } from '@coderline/alphatab/model/MusicFontSymbol'; import type { Note } from '@coderline/alphatab/model/Note'; import type { Voice } from '@coderline/alphatab/model/Voice'; -import { NotationElement } from '@coderline/alphatab/NotationSettings'; import type { ICanvas } from '@coderline/alphatab/platform/ICanvas'; import { BeatXPosition } from '@coderline/alphatab/rendering/BeatXPosition'; import { BarLineGlyph } from '@coderline/alphatab/rendering/glyphs/BarLineGlyph'; @@ -18,6 +17,7 @@ import { NumberedNoteBeatContainerGlyphBase } from '@coderline/alphatab/rendering/glyphs/NumberedDashBeatContainerGlyph'; import { ScoreTimeSignatureGlyph } from '@coderline/alphatab/rendering/glyphs/ScoreTimeSignatureGlyph'; +import { SpacingGlyph } from '@coderline/alphatab/rendering/glyphs/SpacingGlyph'; import { LineBarRenderer } from '@coderline/alphatab/rendering/LineBarRenderer'; import { NumberedBeatContainerGlyph } from '@coderline/alphatab/rendering/NumberedBeatContainerGlyph'; import type { ScoreRenderer } from '@coderline/alphatab/rendering/ScoreRenderer'; @@ -258,9 +258,11 @@ export class NumberedBarRenderer extends LineBarRenderer { this.addPreBeatGlyph(new BarLineGlyph(false, this.bar.staff.track.score.stylesheet.extendBarLines)); } this.createLinePreBeatGlyphs(); - this.createStartSpacing(); - if (this.settings.notation.isNotationElementVisible(NotationElement.BarNumber)) { + const hasSpaceAfterStartGlyphs = this.createStartSpacing(); + if (this.shouldCreateBarNumber()) { this.addPreBeatGlyph(new BarNumberGlyph(0, this.getLineHeight(-0.5), this.bar.index + 1)); + } else if (!hasSpaceAfterStartGlyphs) { + this.addPreBeatGlyph(new SpacingGlyph(0, 0, this.smuflMetrics.oneStaffSpace)); } } diff --git a/packages/alphatab/src/rendering/glyphs/BarNumberGlyph.ts b/packages/alphatab/src/rendering/glyphs/BarNumberGlyph.ts index 544cfb308..c6fb37b41 100644 --- a/packages/alphatab/src/rendering/glyphs/BarNumberGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/BarNumberGlyph.ts @@ -17,13 +17,6 @@ export class BarNumberGlyph extends Glyph { } public override doLayout(): void { - // TODO: activate this and update paddings accordingly. - // if (!this.renderer.staff!.isFirstInSystem) { - // this.width = 0; - // this.height = 0; - // return; - // } - this.renderer.scoreRenderer.canvas!.font = this.renderer.resources.elementFonts.get(NotationElement.BarNumber)!; const size = this.renderer.scoreRenderer.canvas!.measureText(this._number); this.width = size.width; diff --git a/packages/alphatab/test-data/guitarpro8/barnumbers-all.gp b/packages/alphatab/test-data/guitarpro8/barnumbers-all.gp new file mode 100644 index 000000000..fffd5e3c2 Binary files /dev/null and b/packages/alphatab/test-data/guitarpro8/barnumbers-all.gp differ diff --git a/packages/alphatab/test-data/guitarpro8/barnumbers-first.gp b/packages/alphatab/test-data/guitarpro8/barnumbers-first.gp new file mode 100644 index 000000000..8b41f2f08 Binary files /dev/null and b/packages/alphatab/test-data/guitarpro8/barnumbers-first.gp differ diff --git a/packages/alphatab/test-data/guitarpro8/barnumbers-hide.gp b/packages/alphatab/test-data/guitarpro8/barnumbers-hide.gp new file mode 100644 index 000000000..f279f584a Binary files /dev/null and b/packages/alphatab/test-data/guitarpro8/barnumbers-hide.gp differ diff --git a/packages/alphatab/test-data/musicxml-samples/Binchois.png b/packages/alphatab/test-data/musicxml-samples/Binchois.png index 5a45593a5..269a08b61 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/BrookeWestSample.png b/packages/alphatab/test-data/musicxml-samples/BrookeWestSample.png index eb1616a06..2c367f8f2 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/MozartTrio.png b/packages/alphatab/test-data/musicxml-samples/MozartTrio.png index 9824aed5a..bbf99172c 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-testsuite/21e-Chords-PickupMeasures.png b/packages/alphatab/test-data/musicxml-testsuite/21e-Chords-PickupMeasures.png index 889dba04e..2b9fc42cd 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/21e-Chords-PickupMeasures.png and b/packages/alphatab/test-data/musicxml-testsuite/21e-Chords-PickupMeasures.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 da84d910b..b62ac1557 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/46c-Midmeasure-Clef.png b/packages/alphatab/test-data/musicxml-testsuite/46c-Midmeasure-Clef.png index 945adacdb..dc9ce78f2 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 e0e3f74a4..f69622f3e 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/46e-PickupMeasure-SecondVoiceStartsLater.png b/packages/alphatab/test-data/musicxml-testsuite/46e-PickupMeasure-SecondVoiceStartsLater.png index b069136c7..596f67492 100644 Binary files a/packages/alphatab/test-data/musicxml-testsuite/46e-PickupMeasure-SecondVoiceStartsLater.png and b/packages/alphatab/test-data/musicxml-testsuite/46e-PickupMeasure-SecondVoiceStartsLater.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 08aa8e470..08e19a3b3 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/musicxml4/measure-numbering-none.xml b/packages/alphatab/test-data/musicxml4/measure-numbering-none.xml new file mode 100644 index 000000000..8005df275 --- /dev/null +++ b/packages/alphatab/test-data/musicxml4/measure-numbering-none.xml @@ -0,0 +1,86 @@ + + + + Title + + + + + Words + Music + Artist + Copyright + + Tab + Notices + + + + + Track 1 + T1 + + + I1 + + + 1 + 1 + 0.5 + 0.25 + + + + Track 2 + T2 + + + I2 + + + 2 + 2 + 0.25 + 0.5 + + + + Track 3 + T3 + + + I3 + + + 3 + 3 + 0.25 + 0.25 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-measure.xml b/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-measure.xml new file mode 100644 index 000000000..1e1b18b29 --- /dev/null +++ b/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-measure.xml @@ -0,0 +1,70 @@ + + + + Title + + + + + Words + Music + Artist + Copyright + + Tab + Notices + + + + + Track 1 + T1 + + + I1 + + + 1 + 1 + 0.5 + 0.25 + + + + Track 2 + T2 + + + I2 + + + 2 + 2 + 0.25 + 0.5 + + + + + + + + + measure + + + + + + + + + + + + + measure + + + + \ No newline at end of file diff --git a/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-none.xml b/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-none.xml new file mode 100644 index 000000000..37073bc03 --- /dev/null +++ b/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-none.xml @@ -0,0 +1,70 @@ + + + + Title + + + + + Words + Music + Artist + Copyright + + Tab + Notices + + + + + Track 1 + T1 + + + I1 + + + 1 + 1 + 0.5 + 0.25 + + + + Track 2 + T2 + + + I2 + + + 2 + 2 + 0.25 + 0.5 + + + + + + + + + none + + + + + + + + + + + + + none + + + + \ No newline at end of file diff --git a/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-system.xml b/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-system.xml new file mode 100644 index 000000000..d38330b84 --- /dev/null +++ b/packages/alphatab/test-data/musicxml4/partwise-measure-numbering-system.xml @@ -0,0 +1,70 @@ + + + + Title + + + + + Words + Music + Artist + Copyright + + Tab + Notices + + + + + Track 1 + T1 + + + I1 + + + 1 + 1 + 0.5 + 0.25 + + + + Track 2 + T2 + + + I2 + + + 2 + 2 + 0.25 + 0.5 + + + + + + + + + system + + + + + + + + + + + + + system + + + + \ No newline at end of file diff --git a/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-measure.xml b/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-measure.xml new file mode 100644 index 000000000..65c26411d --- /dev/null +++ b/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-measure.xml @@ -0,0 +1,63 @@ + + + + Title + + + + + Words + Music + Artist + Copyright + + Tab + Notices + + + + + Track 1 + T1 + + + I1 + + + 1 + 1 + 0.5 + 0.25 + + + + Track 2 + T2 + + + I2 + + + 2 + 2 + 0.25 + 0.5 + + + + + + + + + + measure + + + + + + + + + \ No newline at end of file diff --git a/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-none.xml b/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-none.xml new file mode 100644 index 000000000..65381f8a4 --- /dev/null +++ b/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-none.xml @@ -0,0 +1,63 @@ + + + + Title + + + + + Words + Music + Artist + Copyright + + Tab + Notices + + + + + Track 1 + T1 + + + I1 + + + 1 + 1 + 0.5 + 0.25 + + + + Track 2 + T2 + + + I2 + + + 2 + 2 + 0.25 + 0.5 + + + + + + + + + + none + + + + + + + + + \ No newline at end of file diff --git a/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-system.xml b/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-system.xml new file mode 100644 index 000000000..8eca9e439 --- /dev/null +++ b/packages/alphatab/test-data/musicxml4/timewise-measure-numbering-system.xml @@ -0,0 +1,63 @@ + + + + Title + + + + + Words + Music + Artist + Copyright + + Tab + Notices + + + + + Track 1 + T1 + + + I1 + + + 1 + 1 + 0.5 + 0.25 + + + + Track 2 + T2 + + + I2 + + + 2 + 2 + 0.25 + 0.5 + + + + + + + + + + system + + + + + + + + + \ No newline at end of file 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 new file mode 100644 index 000000000..73a8ff441 Binary files /dev/null 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 new file mode 100644 index 000000000..9ed05856f Binary files /dev/null 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 new file mode 100644 index 000000000..ef76abb22 Binary files /dev/null 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 new file mode 100644 index 000000000..2b56614e9 Binary files /dev/null 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 new file mode 100644 index 000000000..3ef37bd65 Binary files /dev/null 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 new file mode 100644 index 000000000..4489daebb Binary files /dev/null and b/packages/alphatab/test-data/visual-tests/layout/barnumberdisplay-stylesheet-hide.png differ diff --git a/packages/alphatab/test/importer/AlphaTexImporter.test.ts b/packages/alphatab/test/importer/AlphaTexImporter.test.ts index 13335ec24..1092154ee 100644 --- a/packages/alphatab/test/importer/AlphaTexImporter.test.ts +++ b/packages/alphatab/test/importer/AlphaTexImporter.test.ts @@ -27,6 +27,7 @@ import { NoteOrnament } from '@coderline/alphatab/model/NoteOrnament'; import { Ottavia } from '@coderline/alphatab/model/Ottavia'; import { Rasgueado } from '@coderline/alphatab/model/Rasgueado'; import { + BarNumberDisplay, BracketExtendMode, TrackNameMode, TrackNameOrientation, @@ -2618,4 +2619,38 @@ describe('AlphaTexImporterTest', () => { it('buzzroll-default4', () => test(`C4 {tp (4 buzzRoll)}`)); it('buzzroll-default5', () => test(`C4 {tp (5 buzzRoll)}`)); }); + + describe('defaultBarNumberDisplay', () => { + function test(tex: string, mode: BarNumberDisplay) { + const score = parseTex(tex); + expect(score.stylesheet.barNumberDisplay).to.equal(mode); + + testExportRoundtrip(score); + } + + it('all', () => test('\\defaultBarNumberDisplay allBars C4', BarNumberDisplay.AllBars)); + it('first', () => test('\\defaultBarNumberDisplay firstOfSystem C4', BarNumberDisplay.FirstOfSystem)); + it('hide', () => test('\\defaultBarNumberDisplay hide C4', BarNumberDisplay.Hide)); + }); + + describe('barNumberDisplay', () => { + function test(tex: string, mode: BarNumberDisplay | undefined) { + const score = parseTex(tex); + expect(score.tracks[0].staves[0].bars[0].barNumberDisplay).to.be.undefined; + expect(score.tracks[0].staves[0].bars[1].barNumberDisplay).to.equal(mode); + + testExportRoundtrip(score); + } + + it('unsert', () => test('\\defaultBarNumberDisplay hide C4 | C4 ', undefined)); + it('all', () => + test('\\defaultBarNumberDisplay hide C4 | \\barNumberDisplay allBars C4 ', BarNumberDisplay.AllBars)); + it('first', () => + test( + '\\defaultBarNumberDisplay hide C4 | \\barNumberDisplay firstOfSystem C4 ', + BarNumberDisplay.FirstOfSystem + )); + it('hide', () => + test('\\defaultBarNumberDisplay allBars C4 | \\barNumberDisplay hide C4 ', BarNumberDisplay.Hide)); + }); }); diff --git a/packages/alphatab/test/importer/Gp8Importer.test.ts b/packages/alphatab/test/importer/Gp8Importer.test.ts index 24ca35899..df7f05c64 100644 --- a/packages/alphatab/test/importer/Gp8Importer.test.ts +++ b/packages/alphatab/test/importer/Gp8Importer.test.ts @@ -3,15 +3,21 @@ import { ByteBuffer } from '@coderline/alphatab/io/ByteBuffer'; import { AutomationType } from '@coderline/alphatab/model/Automation'; import { BeatBeamingMode } from '@coderline/alphatab/model/Beat'; import { Direction } from '@coderline/alphatab/model/Direction'; -import { BracketExtendMode, TrackNameMode, TrackNameOrientation, TrackNamePolicy } from '@coderline/alphatab/model/RenderStylesheet'; +import { + BarNumberDisplay, + BracketExtendMode, + TrackNameMode, + TrackNameOrientation, + TrackNamePolicy +} from '@coderline/alphatab/model/RenderStylesheet'; import { ScoreSubElement } from '@coderline/alphatab/model/Score'; import { TextAlign } from '@coderline/alphatab/platform/ICanvas'; import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection'; import { Settings } from '@coderline/alphatab/Settings'; import { SynthConstants } from '@coderline/alphatab/synth/SynthConstants'; +import { expect } from 'chai'; import { GpImporterTestHelper } from 'test/importer/GpImporterTestHelper'; import { TestPlatform } from 'test/TestPlatform'; -import { expect } from 'chai'; describe('Gp8ImporterTest', () => { async function prepareImporterWithFile(name: string): Promise { @@ -425,12 +431,18 @@ describe('Gp8ImporterTest', () => { expect(score.tracks[0].playbackInfo.bank).to.equal(0); expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].automations.length).to.equal(1); - expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].getAutomation(AutomationType.Instrument)?.value).to.equal(25); + expect( + score.tracks[0].staves[0].bars[0].voices[0].beats[0].getAutomation(AutomationType.Instrument)?.value + ).to.equal(25); // expect(score.tracks[0].staves[0].bars[0].voices[0].beats[0].getAutomation(AutomationType.Bank)?.value).to.equal(0); skipped expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].automations.length).to.equal(2); - expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].getAutomation(AutomationType.Instrument)?.value).to.equal(25); - expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].getAutomation(AutomationType.Bank)?.value).to.equal(256); + expect( + score.tracks[0].staves[0].bars[1].voices[0].beats[0].getAutomation(AutomationType.Instrument)?.value + ).to.equal(25); + expect(score.tracks[0].staves[0].bars[1].voices[0].beats[0].getAutomation(AutomationType.Bank)?.value).to.equal( + 256 + ); }); it('extend-bar-lines', async () => { @@ -438,4 +450,19 @@ describe('Gp8ImporterTest', () => { expect(score.stylesheet.extendBarLines).to.be.true; }); + + describe('barnumbers', () => { + it('all', async () => { + const score = (await prepareImporterWithFile('guitarpro8/barnumbers-all.gp')).readScore(); + expect(score.stylesheet.barNumberDisplay).to.equal(BarNumberDisplay.AllBars); + }); + it('hide', async () => { + const score = (await prepareImporterWithFile('guitarpro8/barnumbers-hide.gp')).readScore(); + expect(score.stylesheet.barNumberDisplay).to.equal(BarNumberDisplay.Hide); + }); + it('first', async () => { + const score = (await prepareImporterWithFile('guitarpro8/barnumbers-first.gp')).readScore(); + expect(score.stylesheet.barNumberDisplay).to.equal(BarNumberDisplay.FirstOfSystem); + }); + }); }); diff --git a/packages/alphatab/test/importer/MusicXmlImporter.test.ts b/packages/alphatab/test/importer/MusicXmlImporter.test.ts index 159d20799..b007e246d 100644 --- a/packages/alphatab/test/importer/MusicXmlImporter.test.ts +++ b/packages/alphatab/test/importer/MusicXmlImporter.test.ts @@ -3,6 +3,7 @@ import type { Score } from '@coderline/alphatab/model/Score'; import { BendType } from '@coderline/alphatab/model/BendType'; import { expect } from 'chai'; import { JsonConverter } from '@coderline/alphatab/model/JsonConverter'; +import { BarNumberDisplay } from '@coderline/alphatab/model/RenderStylesheet'; describe('MusicXmlImporterTests', () => { it('track-volume', async () => { @@ -273,4 +274,50 @@ describe('MusicXmlImporterTests', () => { const score = await MusicXmlImporterTestHelper.loadFile('test-data/musicxml4/buzzroll.xml'); expect(score).toMatchSnapshot(); }); + + describe('barnumberdisplay', async () => { + async function testPartwise(filename: string, display: BarNumberDisplay) { + const score = await MusicXmlImporterTestHelper.loadFile(`test-data/musicxml4/${filename}`); + expect(score.tracks[0].staves[0].bars[1].barNumberDisplay).to.equal(display); + expect(score.tracks[1].staves[0].bars[2].barNumberDisplay).to.equal(display); + } + + async function testTimewise(filename: string, display: BarNumberDisplay) { + const score = await MusicXmlImporterTestHelper.loadFile(`test-data/musicxml4/${filename}`); + expect(score.tracks[0].staves[0].bars[1].barNumberDisplay).to.equal(display); + expect(score.tracks[1].staves[0].bars[1].barNumberDisplay).to.equal(display); + } + + it('partwise-none', async () => + await testPartwise('partwise-measure-numbering-none.xml', BarNumberDisplay.Hide)); + it('partwise-measure', async () => + await testPartwise('partwise-measure-numbering-measure.xml', BarNumberDisplay.AllBars)); + it('partwise-system', async () => + await testPartwise('partwise-measure-numbering-system.xml', BarNumberDisplay.FirstOfSystem)); + it('partwise-implicit', async () => { + const score = await MusicXmlImporterTestHelper.loadFile('test-data/musicxml4/partwise-anacrusis.xml'); + expect(score.tracks[0].staves[0].bars[0].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + expect(score.tracks[0].staves[0].bars[1].barNumberDisplay).to.be.undefined; + expect(score.tracks[0].staves[0].bars[3].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + expect(score.tracks[1].staves[0].bars[0].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + expect(score.tracks[1].staves[0].bars[1].barNumberDisplay).to.be.undefined; + expect(score.tracks[1].staves[0].bars[3].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + }); + + it('timewise-none', async () => + await testTimewise('timewise-measure-numbering-none.xml', BarNumberDisplay.Hide)); + it('timewise-measure', async () => + await testTimewise('timewise-measure-numbering-measure.xml', BarNumberDisplay.AllBars)); + it('timewise-system', async () => + await testTimewise('timewise-measure-numbering-system.xml', BarNumberDisplay.FirstOfSystem)); + it('timewise-implicit', async () => { + const score = await MusicXmlImporterTestHelper.loadFile('test-data/musicxml4/timewise-anacrusis.xml'); + expect(score.tracks[0].staves[0].bars[0].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + expect(score.tracks[0].staves[0].bars[1].barNumberDisplay).to.be.undefined; + expect(score.tracks[0].staves[0].bars[3].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + expect(score.tracks[1].staves[0].bars[0].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + expect(score.tracks[1].staves[0].bars[1].barNumberDisplay).to.be.undefined; + expect(score.tracks[1].staves[0].bars[3].barNumberDisplay).to.equal(BarNumberDisplay.Hide); + }); + }); }); diff --git a/packages/alphatab/test/importer/__snapshots__/MusicXmlImporter.test.ts.snap b/packages/alphatab/test/importer/__snapshots__/MusicXmlImporter.test.ts.snap index 5e17d7f36..b55cd2ecf 100644 --- a/packages/alphatab/test/importer/__snapshots__/MusicXmlImporter.test.ts.snap +++ b/packages/alphatab/test/importer/__snapshots__/MusicXmlImporter.test.ts.snap @@ -793,6 +793,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", @@ -850,6 +851,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", @@ -915,6 +917,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", @@ -972,6 +975,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", @@ -3031,6 +3035,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", @@ -3088,6 +3093,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", @@ -3153,6 +3159,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", @@ -3210,6 +3217,7 @@ Map { ], }, ], + "barnumberdisplay" => 2, }, Map { "__kind" => "Bar", diff --git a/packages/alphatab/test/visualTests/features/Layout.test.ts b/packages/alphatab/test/visualTests/features/Layout.test.ts index f45f8d4fe..262fa3573 100644 --- a/packages/alphatab/test/visualTests/features/Layout.test.ts +++ b/packages/alphatab/test/visualTests/features/Layout.test.ts @@ -328,4 +328,90 @@ describe('LayoutTests', () => { } ); }); + + describe('barnumberdisplay', () => { + describe('stylesheet', () => { + it('all', async () => + await VisualTestHelper.runVisualTestTex( + ` + \\defaultBarNumberDisplay allBars + C4.1 | C4.1 | C4.1 | + C4.1 | C4.1 | C4.1 + `, + 'test-data/visual-tests/layout/barnumberdisplay-stylesheet-all.png', + undefined, + o => { + o.settings.display.layoutMode = LayoutMode.Parchment; + } + )); + it('first', async () => + await VisualTestHelper.runVisualTestTex( + ` + \\defaultBarNumberDisplay firstOfSystem + C4.1 | C4.1 | C4.1 | + C4.1 | C4.1 | C4.1 + `, + 'test-data/visual-tests/layout/barnumberdisplay-stylesheet-first.png', + undefined, + o => { + o.settings.display.layoutMode = LayoutMode.Parchment; + } + )); + it('hide', async () => + await VisualTestHelper.runVisualTestTex( + ` + \\defaultBarNumberDisplay hide + C4.1 | C4.1 | C4.1 | + C4.1 | C4.1 | C4.1 + `, + 'test-data/visual-tests/layout/barnumberdisplay-stylesheet-hide.png', + undefined, + o => { + o.settings.display.layoutMode = LayoutMode.Parchment; + } + )); + }); + + describe('bar-override', () => { + it('all', async () => + await VisualTestHelper.runVisualTestTex( + ` + \\defaultBarNumberDisplay allBars + C4.1 | \\barNumberDisplay hide C4.1 | C4.1 | + C4.1 | C4.1 | C4.1 + `, + 'test-data/visual-tests/layout/barnumberdisplay-bar-override-all.png', + undefined, + o => { + o.settings.display.layoutMode = LayoutMode.Parchment; + } + )); + it('first', async () => + await VisualTestHelper.runVisualTestTex( + ` + \\defaultBarNumberDisplay firstOfSystem + C4.1 | \\barNumberDisplay allBars C4.1 | C4.1 | + \\barNumberDisplay hide C4.1 | C4.1 | C4.1 + `, + 'test-data/visual-tests/layout/barnumberdisplay-bar-override-first.png', + undefined, + o => { + o.settings.display.layoutMode = LayoutMode.Parchment; + } + )); + it('hide', async () => + await VisualTestHelper.runVisualTestTex( + ` + \\defaultBarNumberDisplay hide + C4.1 | \\barNumberDisplay allBars C4.1 | C4.1 | + \\barNumberDisplay firstOfSystem C4.1 | \\barNumberDisplay firstOfSystem C4.1 | C4.1 + `, + 'test-data/visual-tests/layout/barnumberdisplay-bar-override-hide.png', + undefined, + o => { + o.settings.display.layoutMode = LayoutMode.Parchment; + } + )); + }); + }); }); diff --git a/packages/alphatex/src/definitions.ts b/packages/alphatex/src/definitions.ts index 979b4c77a..94d527157 100644 --- a/packages/alphatex/src/definitions.ts +++ b/packages/alphatex/src/definitions.ts @@ -148,6 +148,7 @@ import { x } from '@coderline/alphatab-alphatex//properties/note/x'; import { metadata, properties } from '@coderline/alphatab-alphatex/common'; import { db } from '@coderline/alphatab-alphatex/metadata/bar/db'; import { voiceMode } from '@coderline/alphatab-alphatex/metadata/bar/voiceMode'; +import { defaultBarNumberDisplay } from '@coderline/alphatab-alphatex/metadata/score/defaultbarnumberdisplay'; import { chordDiagramsInScore } from '@coderline/alphatab-alphatex/metadata/score/chordDiagramsInScore'; import { extendBarLines } from '@coderline/alphatab-alphatex/metadata/score/extendbarlines'; import { hideEmptyStaves } from '@coderline/alphatab-alphatex/metadata/score/hideemptystaves'; @@ -155,6 +156,7 @@ import { hideEmptyStavesInFirstSystem } from '@coderline/alphatab-alphatex/metad import { showSingleStaffBrackets } from '@coderline/alphatab-alphatex/metadata/score/showsinglestaffbrackets'; import { instrumentMeta } from '@coderline/alphatab-alphatex/metadata/staff/instrument'; import type { AlphaTexExample, WithDescription, WithSignatures } from '@coderline/alphatab-alphatex/types'; +import { barNumberDisplay } from '@coderline/alphatab-alphatex/metadata/bar/barnumberdisplay'; export const structuralMetaData = metadata(track, staff, voice); export const scoreMetaData = metadata( @@ -187,7 +189,8 @@ export const scoreMetaData = metadata( chordDiagramsInScore, hideEmptyStaves, hideEmptyStavesInFirstSystem, - showSingleStaffBrackets + showSingleStaffBrackets, + defaultBarNumberDisplay ); export const staffMetaData = metadata( @@ -226,7 +229,8 @@ export const barMetaData = metadata( sph, spu, db, - voiceMode + voiceMode, + barNumberDisplay ); export const allMetadata = new Map([ diff --git a/packages/alphatex/src/enum.ts b/packages/alphatex/src/enum.ts index 71786b005..014c0516e 100644 --- a/packages/alphatex/src/enum.ts +++ b/packages/alphatex/src/enum.ts @@ -28,7 +28,8 @@ export const alphaTexMappedEnumLookup = { BarLineStyle: alphaTab.model.BarLineStyle, SimileMark: alphaTab.model.SimileMark, Direction: alphaTab.model.Direction, - TremoloPickingStyle: alphaTab.model.TremoloPickingStyle + TremoloPickingStyle: alphaTab.model.TremoloPickingStyle, + BarNumberDisplay: alphaTab.model.BarNumberDisplay }; export type AlphaTexMappedEnumName = keyof typeof alphaTexMappedEnumLookup; @@ -453,6 +454,11 @@ export const alphaTexMappedEnumMapping: { TremoloPickingStyle: { Default: { snippet: 'default', shortDescription: 'Default tremolo' }, BuzzRoll: { snippet: 'buzzRoll', shortDescription: 'Buzz roll tremolo' } + }, + BarNumberDisplay: { + AllBars: { snippet: 'allBars', shortDescription: 'All bars' }, + FirstOfSystem: { snippet: 'firstOfSystem', shortDescription: 'First bar of every system' }, + Hide: { snippet: 'hide', shortDescription: 'Hide' } } }; diff --git a/packages/alphatex/src/metadata/bar/barnumberdisplay.ts b/packages/alphatex/src/metadata/bar/barnumberdisplay.ts new file mode 100644 index 000000000..6cd37c999 --- /dev/null +++ b/packages/alphatex/src/metadata/bar/barnumberdisplay.ts @@ -0,0 +1,41 @@ +import * as alphaTab from '@coderline/alphatab'; +import { enumParameter } from '@coderline/alphatab-alphatex/enum'; +import type { MetadataTagDefinition } from '@coderline/alphatab-alphatex/types'; + +export const barNumberDisplay: MetadataTagDefinition = { + tag: '\\barNumberDisplay', + snippet: '\\barNumberDisplay ${1:allBars}$0', + shortDescription: 'Sets the display mode for bar numbers.', + signatures: [ + { + parameters: [ + { + name: 'mode', + shortDescription: 'The mode to use', + parseMode: alphaTab.importer.alphaTex.ArgumentListParseTypesMode.Required, + ...enumParameter('BarNumberDisplay') + } + ] + } + ], + examples: [ + { + options: { display: { layoutMode: 'Parchment' } }, + tex: ` + \\defaultBarNumberDisplay hide + \\track { defaultsystemslayout 3 } + C4.1 | \\barNumberDisplay allBars C4.1 | C4.1 | + \\barNumberDisplay firstOfSystem C4.1 | \\barNumberDisplay firstOfSystem C4.1 | C4.1 + ` + }, + { + options: { display: { layoutMode: 'Parchment' } }, + tex: ` + \\defaultBarNumberDisplay firstOfSystem + \\track { defaultsystemslayout 3 } + C4.1 | \\barNumberDisplay allBars C4.1 | C4.1 | + \\barNumberDisplay hide C4.1 | C4.1 | C4.1 + ` + } + ] +}; diff --git a/packages/alphatex/src/metadata/score/defaultbarnumberdisplay.ts b/packages/alphatex/src/metadata/score/defaultbarnumberdisplay.ts new file mode 100644 index 000000000..00826ae76 --- /dev/null +++ b/packages/alphatex/src/metadata/score/defaultbarnumberdisplay.ts @@ -0,0 +1,53 @@ +import * as alphaTab from '@coderline/alphatab'; +import { enumParameter } from '@coderline/alphatab-alphatex/enum'; +import type { MetadataTagDefinition } from '@coderline/alphatab-alphatex/types'; + +export const defaultBarNumberDisplay: MetadataTagDefinition = { + tag: '\\defaultBarNumberDisplay', + snippet: '\\defaultBarNumberDisplay ${1:allBars}$0', + shortDescription: 'Sets the display mode for bar numbers on all bars.', + signatures: [ + { + parameters: [ + { + name: 'mode', + shortDescription: 'The mode to use', + parseMode: alphaTab.importer.alphaTex.ArgumentListParseTypesMode.Required, + ...enumParameter('BarNumberDisplay') + } + ] + } + ], + examples: [ + { + options: { display: { layoutMode: 'Parchment' } }, + tex: ` + \\defaultBarNumberDisplay allBars + \\title "All Bars" + \\track { defaultsystemslayout 3 } + C4.1 | C4.1 | C4.1 | + C4.1 | C4.1 | C4.1 + ` + }, + { + options: { display: { layoutMode: 'Parchment' } }, + tex: ` + \\defaultBarNumberDisplay firstOfSystem + \\title "First of System" + \\track { defaultsystemslayout 3 } + C4.1 | C4.1 | C4.1 | + C4.1 | C4.1 | C4.1 + ` + }, + { + options: { display: { layoutMode: 'Parchment' } }, + tex: ` + \\defaultBarNumberDisplay hide + \\title "Hide" + \\track { defaultsystemslayout 3 } + C4.1 | C4.1 | C4.1 | + C4.1 | C4.1 | C4.1 + ` + } + ] +}; diff --git a/packages/csharp/src/AlphaTab.Test/Test/Globals.cs b/packages/csharp/src/AlphaTab.Test/Test/Globals.cs index 69d5f93ce..2c77499f1 100644 --- a/packages/csharp/src/AlphaTab.Test/Test/Globals.cs +++ b/packages/csharp/src/AlphaTab.Test/Test/Globals.cs @@ -55,11 +55,11 @@ public static string UseSnapshotValue(string baseName, string hint) internal class NotExpector { - private readonly T _actual; + private readonly T? _actual; public NotExpector Be => this; private readonly string? _message; - public NotExpector(T actual, string? message = null) + public NotExpector(T? actual, string? message = null) { _actual = actual; _message = message; @@ -76,14 +76,19 @@ public void Ok() Assert.AreEqual(default!, _actual, _message); } } + + public void Undefined() + { + Assert.IsNotNull(_actual, _message); + } } internal class Expector { - private readonly T _actual; + private readonly T? _actual; private readonly string? _message; - public Expector(T actual, string? message = null) + public Expector(T? actual, string? message = null) { _actual = actual; _message = message; @@ -167,6 +172,11 @@ public void Ok() Assert.AreNotEqual(default!, _actual, _message); } + public void Undefined() + { + Assert.IsNull(_actual, _message); + } + public void Length(int length) { if (_actual is ICollection collection) diff --git a/packages/kotlin/src/android/src/test/java/alphaTab/core/TestGlobals.kt b/packages/kotlin/src/android/src/test/java/alphaTab/core/TestGlobals.kt index 648709268..c8827432e 100644 --- a/packages/kotlin/src/android/src/test/java/alphaTab/core/TestGlobals.kt +++ b/packages/kotlin/src/android/src/test/java/alphaTab/core/TestGlobals.kt @@ -47,6 +47,12 @@ class NotExpector(private val actual: T, private val message: String? = null) } } } + + + fun undefined() { + Assert.assertNotNull(message, actual) + } + } class Expector(private val actual: T, private val message: String? = null) { @@ -157,6 +163,10 @@ class Expector(private val actual: T, private val message: String? = null) { } } + fun undefined() { + Assert.assertNull(message, actual) + } + fun `true`() { if (actual is Boolean) { Assert.assertTrue(message, actual); diff --git a/packages/playground/alphatex-editor.ts b/packages/playground/alphatex-editor.ts index 65310a596..cfa64263a 100644 --- a/packages/playground/alphatex-editor.ts +++ b/packages/playground/alphatex-editor.ts @@ -11,7 +11,7 @@ import { setupControl } from './control'; async function setupLspAlphaTexLanguageSupport(editor: monaco.editor.IStandaloneCodeEditor) { await basicEditorLspIntegration( editor, - new Worker(new URL('./alphaTexLanguageServerWrap', import.meta.url), { type: 'module' }), + new Worker(new URL('./alphatexLanguageServerWrap', import.meta.url), { type: 'module' }), { logger: { error(message) {