diff --git a/packages/alphatab/src/exporter/AlphaTexExporter.ts b/packages/alphatab/src/exporter/AlphaTexExporter.ts index 27dd5c55b..57d290dff 100644 --- a/packages/alphatab/src/exporter/AlphaTexExporter.ts +++ b/packages/alphatab/src/exporter/AlphaTexExporter.ts @@ -17,7 +17,9 @@ import { type AlphaTexScoreNode, type AlphaTexStringLiteral, type AlphaTexArgumentList, - type IAlphaTexAstNode + type IAlphaTexAstNode, + AlphaTexDotTokenNode, + AlphaTexAtTokenNode } from '@coderline/alphatab/importer/alphaTex/AlphaTexAst'; import type { IAlphaTexLanguageImportHandler } from '@coderline/alphatab/importer/alphaTex/IAlphaTexLanguageImportHandler'; import { IOHelper } from '@coderline/alphatab/io/IOHelper'; @@ -181,7 +183,7 @@ class AlphaTexPrinter { this._writeComments(n.leadingComments); this._writeValue(n.noteValue); - this._writeToken(n.noteStringDot, false); + this._writeToken(n.noteStringSeparator, false); this._writeValue(n.noteString); if (n.noteEffects) { @@ -378,6 +380,9 @@ class AlphaTexPrinter { case AlphaTexNodeType.Dot: this._writer.write('.'); break; + case AlphaTexNodeType.At: + this._writer.write('@'); + break; case AlphaTexNodeType.Backslash: this._writer.write('\\'); break; @@ -658,6 +663,17 @@ export class AlphaTexExporter extends ScoreExporter { nodeType: AlphaTexNodeType.String, text: PercussionMapper.getArticulationName(data) } as AlphaTexStringLiteral; + + if (!Number.isNaN(data.string)) { + note.noteStringSeparator = { + nodeType: AlphaTexNodeType.At + } as AlphaTexAtTokenNode; + const stringNumber = data.beat.voice.bar.staff.tuning.length - data.string + 1; + note.noteString = { + nodeType: AlphaTexNodeType.Number, + value: stringNumber + }; + } } else if (data.isPiano) { note.noteValue = { nodeType: AlphaTexNodeType.Ident, @@ -668,9 +684,9 @@ export class AlphaTexExporter extends ScoreExporter { nodeType: AlphaTexNodeType.Number, value: data.fret } as AlphaTexNumberLiteral; - note.noteStringDot = { + note.noteStringSeparator = { nodeType: AlphaTexNodeType.Dot - }; + } as AlphaTexDotTokenNode; const stringNumber = data.beat.voice.bar.staff.tuning.length - data.string + 1; note.noteString = { nodeType: AlphaTexNodeType.Number, diff --git a/packages/alphatab/src/exporter/GpifWriter.ts b/packages/alphatab/src/exporter/GpifWriter.ts index b00ed8737..39d33d6ef 100644 --- a/packages/alphatab/src/exporter/GpifWriter.ts +++ b/packages/alphatab/src/exporter/GpifWriter.ts @@ -290,6 +290,10 @@ export class GpifWriter { } } + if (note.isPercussion) { + this._writeSimplePropertyNode(properties, 'String', 'String', (note.string - 1).toString()); + } + if (note.isPiano) { this._writeSimplePropertyNode(properties, 'Octave', 'Number', note.octave.toString()); this._writeSimplePropertyNode(properties, 'Tone', 'Step', note.tone.toString()); diff --git a/packages/alphatab/src/importer/AlphaTexImporter.ts b/packages/alphatab/src/importer/AlphaTexImporter.ts index 7db2ff544..853b8cce1 100644 --- a/packages/alphatab/src/importer/AlphaTexImporter.ts +++ b/packages/alphatab/src/importer/AlphaTexImporter.ts @@ -542,10 +542,10 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter // Note value let isDead: boolean = false; let isTie: boolean = false; - let numericValue: number = -1; + let numericValue: number = Number.NaN; let articulationValue: string = ''; - let octave: number = -1; - let tone: number = -1; + let octave: number = Number.NaN; + let tone: number = Number.NaN; let accidentalMode = NoteAccidentalMode.Default; const noteValue = node.noteValue as AlphaTexAstNode; let detectedNoteKind: AlphaTexStaffNoteKind | undefined = undefined; @@ -570,7 +570,7 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter isTie = str === '-'; if (isTie || isDead) { numericValue = 0; - if (node.noteStringDot && node.noteString) { + if (node.noteStringSeparator && node.noteString) { detectedNoteKind = AlphaTexStaffNoteKind.Fretted; } else { detectedNoteKind = undefined; // don't know on those notes @@ -668,19 +668,19 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter return; } - const noteString: number = node.noteString!.value; - if (noteString < 1 || noteString > this._state.currentStaff!.tuning.length) { + const frettedNoteString: number = node.noteString!.value; + if (frettedNoteString < 1 || frettedNoteString > this._state.currentStaff!.tuning.length) { this.addSemanticDiagnostic({ code: AlphaTexDiagnosticCode.AT208, message: `Note string is out of range. Available range: 1-${this._state.currentStaff!.tuning.length}`, severity: AlphaTexDiagnosticsSeverity.Error, - start: noteValue.end, - end: noteValue.end + start: node.noteString.start, + end: node.noteString.end }); return; } - note.string = this._state.currentStaff!.tuning.length - (noteString - 1); + note.string = this._state.currentStaff!.tuning.length - (frettedNoteString - 1); if (!isTie) { note.fret = numericValue; } @@ -716,6 +716,34 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter this._state.articulationUniqueIdToIndex.set(articulationValue, articulationIndex); } note.percussionArticulation = articulationIndex; + + if (node.noteString) { + const percussionNoteString: number = node.noteString!.value; + if (percussionNoteString < 1 || percussionNoteString > this._state.currentStaff!.tuning.length) { + this.addSemanticDiagnostic({ + code: AlphaTexDiagnosticCode.AT208, + message: `Note string is out of range. Available range: 1-${this._state.currentStaff!.tuning.length}`, + severity: AlphaTexDiagnosticsSeverity.Error, + start: node.noteString.start, + end: node.noteString.end + }); + return; + } + note.string = this._state.currentStaff!.tuning.length - (percussionNoteString - 1); + } else { + // find free string + for (let i = 0; i < this._state.currentStaff!.tuning.length; i++) { + const s = this._state.currentStaff!.tuning.length - i; + if (!beat.noteStringLookup.has(s)) { + note.string = s; + break; + } + } + if (Number.isNaN(note.string)) { + note.string = this._state.currentStaff!.tuning.length; + } + } + break; } } @@ -742,6 +770,7 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter switch (staffNoteKind) { case AlphaTexStaffNoteKind.Pitched: staff.isPercussion = false; + staff.showTablature = false; staff.stringTuning.reset(); if (!this._state.staffHasExplicitDisplayTransposition.has(staff)) { staff.displayTranspositionPitch = 0; @@ -755,6 +784,7 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter case AlphaTexStaffNoteKind.Articulation: staff.isPercussion = true; staff.stringTuning.reset(); + staff.stringTuning.tunings = [0, 0, 0, 0, 0, 0]; if (!this._state.staffHasExplicitDisplayTransposition.has(staff)) { staff.displayTranspositionPitch = 0; } @@ -813,7 +843,9 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter // reset to defaults staff.stringTuning.reset(); - if (program === 15) { + if (staff.isPercussion) { + staff.stringTuning.tunings = [0, 0, 0, 0, 0, 0]; + } else if (program === 15) { // dulcimer E4 B3 G3 D3 A2 E2 staff.stringTuning.tunings = Tuning.getDefaultTuningFor(6)!.tunings; } else if (program >= 24 && program <= 31) { @@ -1097,7 +1129,6 @@ export class AlphaTexImporter extends ScoreImporter implements IAlphaTexImporter public applyPercussionStaff(staff: Staff) { staff.isPercussion = true; - staff.showTablature = false; staff.track.playbackInfo.program = 0; } diff --git a/packages/alphatab/src/importer/Gp3To5Importer.ts b/packages/alphatab/src/importer/Gp3To5Importer.ts index 949838be2..415e28516 100644 --- a/packages/alphatab/src/importer/Gp3To5Importer.ts +++ b/packages/alphatab/src/importer/Gp3To5Importer.ts @@ -1401,8 +1401,7 @@ export class Gp3To5Importer extends ScoreImporter { newNote.percussionArticulation = Gp3To5Importer._gp5PercussionInstrumentMap.has(newNote.fret) ? Gp3To5Importer._gp5PercussionInstrumentMap.get(newNote.fret)! : newNote.fret; - newNote.string = -1; - newNote.fret = -1; + newNote.fret = Number.NaN; } if (swapAccidentals) { const accidental = ModelUtils.computeAccidental( diff --git a/packages/alphatab/src/importer/GpifParser.ts b/packages/alphatab/src/importer/GpifParser.ts index f5954de2c..816086db9 100644 --- a/packages/alphatab/src/importer/GpifParser.ts +++ b/packages/alphatab/src/importer/GpifParser.ts @@ -606,8 +606,6 @@ export class GpifParser { const track: Track = new Track(); track.ensureStaveCount(1); - const staff: Staff = track.staves[0]; - staff.showStandardNotation = true; const trackId: string = node.getAttribute('id'); for (const c of node.childElements()) { @@ -979,10 +977,6 @@ export class GpifParser { } } - if (!staff.isPercussion) { - staff.showTablature = true; - } - break; case 'DiagramCollection': case 'ChordCollection': @@ -1161,8 +1155,6 @@ export class GpifParser { } for (const staff of track.staves) { staff.stringTuning.tunings = tuning; - staff.showStandardNotation = true; - staff.showTablature = true; } break; case 'DiagramCollection': @@ -2437,7 +2429,7 @@ export class GpifParser { case 'Octave': note.octave = GpifParser._parseIntSafe(c.findChildElement('Number')?.innerText, 0); // when exporting GP6 from GP7 the tone might be missing - if (note.tone === -1) { + if (Number.isNaN(note.tone)) { note.tone = 0; } break; @@ -2779,12 +2771,10 @@ export class GpifParser { for (const noteId of this._notesOfBeat.get(beatId)!) { if (noteId !== GpifParser._invalidId) { const note = NoteCloner.clone(this._noteById.get(noteId)!); - // reset midi value for non-percussion staves if (staff.isPercussion) { - note.fret = -1; - note.string = -1; + note.fret = Number.NaN; } else { - note.percussionArticulation = -1; + note.percussionArticulation = Number.NaN; } beat.addNote(note); if (this._tappedNotes.has(noteId)) { diff --git a/packages/alphatab/src/importer/MusicXmlImporter.ts b/packages/alphatab/src/importer/MusicXmlImporter.ts index 4742ccce4..97ae26c03 100644 --- a/packages/alphatab/src/importer/MusicXmlImporter.ts +++ b/packages/alphatab/src/importer/MusicXmlImporter.ts @@ -1903,8 +1903,9 @@ export class MusicXmlImporter extends ScoreImporter { break; case 'percussion': bar.clef = Clef.Neutral; - if(bar.index === 0){ + if (bar.index === 0) { bar.staff.isPercussion = true; + bar.staff.showTablature = false; } break; case 'tab': diff --git a/packages/alphatab/src/importer/PartConfiguration.ts b/packages/alphatab/src/importer/PartConfiguration.ts index 3b8e08b14..eae655136 100644 --- a/packages/alphatab/src/importer/PartConfiguration.ts +++ b/packages/alphatab/src/importer/PartConfiguration.ts @@ -70,9 +70,7 @@ export class PartConfiguration { if (trackIndex < score.tracks.length) { const track: Track = score.tracks[trackIndex]; for (const staff of track.staves) { - if(!staff.isPercussion){ - staff.showTablature = trackConfig.showTablature; - } + staff.showTablature = trackConfig.showTablature; staff.showStandardNotation = trackConfig.showStandardNotation; staff.showSlash = trackConfig.showSlash; staff.showNumbered = trackConfig.showNumbered; diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts index cbda1438a..0ab0b5484 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageDefinitions.ts @@ -80,9 +80,9 @@ export class AlphaTex1LanguageDefinitions { 'title', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -90,9 +90,9 @@ export class AlphaTex1LanguageDefinitions { 'subtitle', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -100,9 +100,9 @@ export class AlphaTex1LanguageDefinitions { 'artist', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -110,9 +110,9 @@ export class AlphaTex1LanguageDefinitions { 'album', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -120,9 +120,9 @@ export class AlphaTex1LanguageDefinitions { 'words', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -130,9 +130,9 @@ export class AlphaTex1LanguageDefinitions { 'music', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -140,8 +140,8 @@ export class AlphaTex1LanguageDefinitions { 'wordsandmusic', [ [ - [[17], 0], - [[10, 17], 1, ['left', 'center', 'right']] + [[107], 0], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -149,9 +149,9 @@ export class AlphaTex1LanguageDefinitions { 'copyright', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], @@ -159,87 +159,87 @@ export class AlphaTex1LanguageDefinitions { 'copyright2', [ [ - [[17], 0], - [[10, 17], 1, ['left', 'center', 'right']] + [[107], 0], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], - ['instructions', [[[[17, 10], 0]]]], - ['notices', [[[[17, 10], 0]]]], + ['instructions', [[[[107, 100], 0]]]], + ['notices', [[[[107, 100], 0]]]], [ 'tab', [ [ - [[17, 10], 0], - [[17], 1], - [[10, 17], 1, ['left', 'center', 'right']] + [[107, 100], 0], + [[107], 1], + [[100, 107], 1, ['left', 'center', 'right']] ] ] ], - ['systemslayout', [[[[16], 5]]]], - ['defaultsystemslayout', [[[[16], 0]]]], + ['systemslayout', [[[[106], 5]]]], + ['defaultsystemslayout', [[[[106], 0]]]], ['showdynamics', null], ['hidedynamics', null], ['usesystemsignseparator', null], - ['tuningdisplaymode', [[[[10, 17], 0, ['score', 'staff']]]]], + ['tuningdisplaymode', [[[[100, 107], 0, ['score', 'staff']]]]], ['multibarrest', null], - ['bracketextendmode', [[[[10, 17], 0, ['nobrackets', 'groupstaves', 'groupsimilarinstruments']]]]], - ['singletracktracknamepolicy', [[[[10, 17], 0, ['hidden', 'firstsystem', 'allsystems']]]]], - ['multitracktracknamepolicy', [[[[10, 17], 0, ['hidden', 'firstsystem', 'allsystems']]]]], - ['firstsystemtracknamemode', [[[[10, 17], 0, ['fullname', 'shortname']]]]], - ['othersystemstracknamemode', [[[[10, 17], 0, ['fullname', 'shortname']]]]], - ['firstsystemtracknameorientation', [[[[10, 17], 0, ['horizontal', 'vertical']]]]], - ['othersystemstracknameorientation', [[[[10, 17], 0, ['horizontal', 'vertical']]]]], + ['bracketextendmode', [[[[100, 107], 0, ['nobrackets', 'groupstaves', 'groupsimilarinstruments']]]]], + ['singletracktracknamepolicy', [[[[100, 107], 0, ['hidden', 'firstsystem', 'allsystems']]]]], + ['multitracktracknamepolicy', [[[[100, 107], 0, ['hidden', 'firstsystem', 'allsystems']]]]], + ['firstsystemtracknamemode', [[[[100, 107], 0, ['fullname', 'shortname']]]]], + ['othersystemstracknamemode', [[[[100, 107], 0, ['fullname', 'shortname']]]]], + ['firstsystemtracknameorientation', [[[[100, 107], 0, ['horizontal', 'vertical']]]]], + ['othersystemstracknameorientation', [[[[100, 107], 0, ['horizontal', 'vertical']]]]], ['extendbarlines', null], - ['chorddiagramsinscore', [[[[10], 1, ['true', 'false']]]]], + ['chorddiagramsinscore', [[[[100], 1, ['true', 'false']]]]], ['hideemptystaves', null], ['hideemptystavesinfirstsystem', null], ['showsinglestaffbrackets', null], - ['defaultbarnumberdisplay', [[[[10, 17], 0, ['allbars', 'firstofsystem', 'hide']]]]] + ['defaultbarnumberdisplay', [[[[100, 107], 0, ['allbars', 'firstofsystem', 'hide']]]]] ]); public static readonly staffMetaDataSignatures = AlphaTex1LanguageDefinitions._signatures([ - ['tuning', [[[[10, 17], 0, ['piano', 'none', 'voice']]], [[[10, 17], 5]]]], + ['tuning', [[[[100, 107], 0, ['piano', 'none', 'voice']]], [[[100, 107], 5]]]], [ 'chord', [ [ - [[17, 10], 0], - [[10, 17, 16], 5] + [[107, 100], 0], + [[100, 107, 106], 5] ] ] ], - ['capo', [[[[16], 0]]]], + ['capo', [[[[106], 0]]]], [ 'lyrics', [ - [[[17], 0]], + [[[107], 0]], [ - [[16], 0], - [[17], 0] + [[106], 0], + [[107], 0] ] ] ], [ 'articulation', [ - [[[10], 0, ['defaults']]], + [[[100], 0, ['defaults']]], [ - [[17, 10], 0], - [[16], 0] + [[107, 100], 0], + [[106], 0] ] ] ], - ['displaytranspose', [[[[16], 0]]]], - ['transpose', [[[[16], 0]]]], - ['instrument', [[[[16], 0]], [[[17, 10], 0]], [[[10], 0, ['percussion']]]]] + ['displaytranspose', [[[[106], 0]]]], + ['transpose', [[[[106], 0]]]], + ['instrument', [[[[106], 0]], [[[107, 100], 0]], [[[100], 0, ['percussion']]]]] ]); public static readonly structuralMetaDataSignatures = AlphaTex1LanguageDefinitions._signatures([ [ 'track', [ [ - [[17], 1], - [[17], 1] + [[107], 1], + [[107], 1] ] ] ], @@ -250,22 +250,22 @@ export class AlphaTex1LanguageDefinitions { [ 'ts', [ - [[[10, 17], 0, ['common']]], + [[[100, 107], 0, ['common']]], [ - [[16], 0], - [[16], 0] + [[106], 0], + [[106], 0] ] ] ], ['ro', null], - ['rc', [[[[16], 0]]]], - ['ae', [[[[16, 13], 4]]]], + ['rc', [[[[106], 0]]]], + ['ae', [[[[106, 103], 4]]]], [ 'ks', [ [ [ - [10, 17], + [100, 107], 0, [ 'cb', @@ -320,16 +320,16 @@ export class AlphaTex1LanguageDefinitions { ] ] ], - ['clef', [[[[10, 16, 17], 0, ['neutral', 'c3', 'c4', 'f4', 'g2', 'n', 'alto', 'tenor', 'bass', 'treble']]]]], - ['ottava', [[[[10, 17], 0, ['15ma', '8va', 'regular', '8vb', '15mb', '15ma', '8va', '8vb', '15mb']]]]], + ['clef', [[[[100, 106, 107], 0, ['neutral', 'c3', 'c4', 'f4', 'g2', 'n', 'alto', 'tenor', 'bass', 'treble']]]]], + ['ottava', [[[[100, 107], 0, ['15ma', '8va', 'regular', '8vb', '15mb', '15ma', '8va', '8vb', '15mb']]]]], [ 'tempo', [ [ - [[16], 2], - [[17], 1] + [[106], 2], + [[107], 1] ], - [null, [[16], 2], [[17], 0], [[16], 1], [[10], 1, ['hide']]] + [null, [[106], 2], [[107], 0], [[106], 1], [[100], 1, ['hide']]] ] ], [ @@ -337,7 +337,7 @@ export class AlphaTex1LanguageDefinitions { [ [ [ - [10, 16, 17], + [100, 106, 107], 0, [ 'none', @@ -371,10 +371,10 @@ export class AlphaTex1LanguageDefinitions { [ 'section', [ - [[[17, 10], 0]], + [[[107, 100], 0]], [ - [[17, 10], 0], - [[17, 10], 0, null, ['x', '-', 'r']] + [[107, 100], 0], + [[107, 100], 0, null, ['x', '-', 'r']] ] ] ], @@ -383,7 +383,7 @@ export class AlphaTex1LanguageDefinitions { [ [ [ - [10, 17], + [100, 107], 0, [ 'fine', @@ -411,13 +411,13 @@ export class AlphaTex1LanguageDefinitions { ] ], ['ft', null], - ['simile', [[[[10, 17], 0, ['none', 'simple', 'firstofdouble', 'secondofdouble']]]]], + ['simile', [[[[100, 107], 0, ['none', 'simple', 'firstofdouble', 'secondofdouble']]]]], [ 'barlineleft', [ [ [ - [10, 17], + [100, 107], 0, [ 'automatic', @@ -442,7 +442,7 @@ export class AlphaTex1LanguageDefinitions { [ [ [ - [10, 17], + [100, 107], 0, [ 'automatic', @@ -462,32 +462,32 @@ export class AlphaTex1LanguageDefinitions { ] ] ], - ['scale', [[[[16], 2]]]], - ['width', [[[[16], 2]]]], + ['scale', [[[[106], 2]]]], + ['width', [[[[106], 2]]]], [ 'sync', [ [ - [[16], 0], - [[16], 0], - [[16], 0], - [[16], 3] + [[106], 0], + [[106], 0], + [[106], 0], + [[106], 3] ] ] ], - ['accidentals', [[[[10, 17], 0, ['auto', 'explicit']]]]], - ['spd', [[[[16], 2]]]], - ['sph', [[[[16], 2]]]], - ['spu', [[[[16], 2]]]], + ['accidentals', [[[[100, 107], 0, ['auto', 'explicit']]]]], + ['spd', [[[[106], 2]]]], + ['sph', [[[[106], 2]]]], + ['spu', [[[[106], 2]]]], ['db', null], - ['voicemode', [[[[10, 17], 0, ['staffwise', 'barwise']]]]], - ['barnumberdisplay', [[[[10, 17], 0, ['allbars', 'firstofsystem', 'hide']]]]], + ['voicemode', [[[[100, 107], 0, ['staffwise', 'barwise']]]]], + ['barnumberdisplay', [[[[100, 107], 0, ['allbars', 'firstofsystem', 'hide']]]]], [ 'beaming', [ [ - [[16], 0], - [[16], 5] + [[106], 0], + [[106], 5] ] ] ] @@ -496,22 +496,22 @@ export class AlphaTex1LanguageDefinitions { [ 'track', [ - ['color', [[[[17], 0]]]], - ['systemslayout', [[[[16], 5]]]], - ['defaultsystemslayout', [[[[16], 0]]]], + ['color', [[[[107], 0]]]], + ['systemslayout', [[[[106], 5]]]], + ['defaultsystemslayout', [[[[106], 0]]]], ['solo', null], ['mute', null], - ['volume', [[[[16], 0]]]], - ['balance', [[[[16], 0]]]], - ['instrument', [[[[16], 0]], [[[17, 10], 0]], [[[10], 0, ['percussion']]]]], - ['bank', [[[[16], 0]]]], + ['volume', [[[[106], 0]]]], + ['balance', [[[[106], 0]]]], + ['instrument', [[[[106], 0]], [[[107, 100], 0]], [[[100], 0, ['percussion']]]]], + ['bank', [[[[106], 0]]]], ['multibarrest', null] ] ], [ 'staff', [ - ['score', [[[[16], 1]]]], + ['score', [[[[106], 1]]]], ['tabs', null], ['slash', null], ['numbered', null] @@ -554,25 +554,25 @@ export class AlphaTex1LanguageDefinitions { 'tuning', [ ['hide', null], - ['label', [[[[17], 0]]]] + ['label', [[[[107], 0]]]] ] ], [ 'chord', [ - ['firstfret', [[[[16], 0]]]], - ['barre', [[[[16], 5]]]], + ['firstfret', [[[[106], 0]]]], + ['barre', [[[[106], 5]]]], [ 'showdiagram', - [[], [[[17], 0, ['true', 'false']]], [[[10], 0, ['true', 'false']]], [[[16], 0, ['1', '0']]]] + [[], [[[107], 0, ['true', 'false']]], [[[100], 0, ['true', 'false']]], [[[106], 0, ['1', '0']]]] ], [ 'showfingering', - [[], [[[17], 0, ['true', 'false']]], [[[10], 0, ['true', 'false']]], [[[16], 0, ['1', '0']]]] + [[], [[[107], 0, ['true', 'false']]], [[[100], 0, ['true', 'false']]], [[[106], 0, ['1', '0']]]] ], [ 'showname', - [[], [[[17], 0, ['true', 'false']]], [[[10], 0, ['true', 'false']]], [[[16], 0, ['1', '0']]]] + [[], [[[107], 0, ['true', 'false']]], [[[100], 0, ['true', 'false']]], [[[106], 0, ['1', '0']]]] ] ] ], @@ -620,10 +620,10 @@ export class AlphaTex1LanguageDefinitions { [ 'tu', [ - [[[16], 0, ['3', '5', '6', '7', '9', '10', '12']]], + [[[106], 0, ['3', '5', '6', '7', '9', '10', '12']]], [ - [[16], 0], - [[16], 0] + [[106], 0], + [[106], 0] ] ] ] @@ -658,75 +658,74 @@ export class AlphaTex1LanguageDefinitions { [ 'tu', [ - [[[16], 0, ['3', '5', '6', '7', '9', '10', '12']]], + [[[106], 0, ['3', '5', '6', '7', '9', '10', '12']]], [ - [[16], 0], - [[16], 0] + [[106], 0], + [[106], 0] ] ] ], - ['txt', [[[[17, 10], 0]]]], - ['restdisplaypitch', [[[[10, 17], 0]]]], + ['txt', [[[[107, 100], 0]]]], [ 'lyrics', [ - [[[17], 0]], + [[[107], 0]], [ - [[16], 0], - [[17], 0] + [[106], 0], + [[107], 0] ] ] ], [ 'tb', [ - [[[16], 5]], + [[[106], 5]], [ - [[10, 17], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], - [[16], 5] + [[100, 107], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], + [[106], 5] ], [ - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ], [ - [[10, 17], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ] ] ], [ 'tbe', [ - [[[16], 5]], + [[[106], 5]], [ - [[10, 17], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], - [[16], 5] + [[100, 107], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], + [[106], 5] ], [ - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ], [ - [[10, 17], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['custom', 'dive', 'dip', 'hold', 'predive', 'predivedive']], + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ] ] ], - ['bu', [[[[16], 1]]]], - ['bd', [[[[16], 1]]]], - ['au', [[[[16], 1]]]], - ['ad', [[[[16], 1]]]], - ['ch', [[[[17, 10], 0]]]], - ['gr', [[[[10, 17], 1, ['onbeat', 'beforebeat', 'bendgrace', 'ob', 'bb', 'b']]]]], + ['bu', [[[[106], 1]]]], + ['bd', [[[[106], 1]]]], + ['au', [[[[106], 1]]]], + ['ad', [[[[106], 1]]]], + ['ch', [[[[107, 100], 0]]]], + ['gr', [[[[100, 107], 1, ['onbeat', 'beforebeat', 'bendgrace', 'ob', 'bb', 'b']]]]], [ 'dy', [ [ [ - [10, 17], + [100, 107], 0, [ 'ppp', @@ -764,24 +763,24 @@ export class AlphaTex1LanguageDefinitions { 'tempo', [ [ - [[16], 0], - [[10], 1, ['hide']] + [[106], 0], + [[100], 1, ['hide']] ], [ - [[16], 0], - [[17], 0], - [[10], 1, ['hide']] + [[106], 0], + [[107], 0], + [[100], 1, ['hide']] ] ] ], - ['volume', [[[[16], 0]]]], - ['balance', [[[[16], 0]]]], + ['volume', [[[[106], 0]]]], + ['balance', [[[[106], 0]]]], [ 'tp', [ [ - [[16], 0], - [[10, 17], 1, ['default', 'buzzroll']] + [[106], 0], + [[100, 107], 1, ['default', 'buzzroll']] ] ] ], @@ -789,8 +788,8 @@ export class AlphaTex1LanguageDefinitions { 'barre', [ [ - [[16], 0], - [[10, 17], 1, ['full', 'half']] + [[106], 0], + [[100, 107], 1, ['full', 'half']] ] ] ], @@ -799,7 +798,7 @@ export class AlphaTex1LanguageDefinitions { [ [ [ - [10, 17], + [100, 107], 0, [ 'ii', @@ -825,27 +824,28 @@ export class AlphaTex1LanguageDefinitions { ] ] ], - ['ot', [[[[10, 17], 0, ['15ma', '8va', 'regular', '8vb', '15mb', '15ma', '8va', '8vb', '15mb']]]]], - ['instrument', [[[[16], 0]], [[[17, 10], 0]], [[[10], 0, ['percussion']]]]], - ['bank', [[[[16], 0]]]], + ['ot', [[[[100, 107], 0, ['15ma', '8va', 'regular', '8vb', '15mb', '15ma', '8va', '8vb', '15mb']]]]], + ['instrument', [[[[106], 0]], [[[107, 100], 0]], [[[100], 0, ['percussion']]]]], + ['bank', [[[[106], 0]]]], [ 'fermata', [ [ - [[10, 17], 0, ['short', 'medium', 'long']], - [[16], 3] + [[100, 107], 0, ['short', 'medium', 'long']], + [[106], 3] ] ] ], - ['beam', [[[[10, 17], 0, ['invert', 'up', 'down', 'auto', 'split', 'merge', 'splitsecondary']]]]] + ['beam', [[[[100, 107], 0, ['invert', 'up', 'down', 'auto', 'split', 'merge', 'splitsecondary']]]]], + ['restdisplaypitch', [[[[100], 0]]]] ]); public static readonly noteProperties = AlphaTex1LanguageDefinitions._props([ ['nh', null], - ['ah', [[[[16], 1]]]], - ['th', [[[[16], 1]]]], - ['ph', [[[[16], 1]]]], - ['sh', [[[[16], 1]]]], - ['fh', [[[[16], 1]]]], + ['ah', [[[[106], 1]]]], + ['th', [[[[106], 1]]]], + ['ph', [[[[106], 1]]]], + ['sh', [[[[106], 1]]]], + ['fh', [[[[106], 1]]]], ['v', null], ['vw', null], ['sl', null], @@ -866,8 +866,8 @@ export class AlphaTex1LanguageDefinitions { 'tr', [ [ - [[16], 0], - [[16], 1, ['16', '32', '64']] + [[106], 0], + [[106], 1, ['16', '32', '64']] ] ] ], @@ -885,65 +885,65 @@ export class AlphaTex1LanguageDefinitions { [ 'b', [ - [[[16], 5]], + [[[106], 5]], [ [ - [10, 17], + [100, 107], 0, ['custom', 'bend', 'release', 'bendrelease', 'hold', 'prebend', 'prebendbend', 'prebendrelease'] ], - [[16], 5] + [[106], 5] ], [ - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ], [ [ - [10, 17], + [100, 107], 0, ['custom', 'bend', 'release', 'bendrelease', 'hold', 'prebend', 'prebendbend', 'prebendrelease'] ], - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ] ] ], [ 'be', [ - [[[16], 5]], + [[[106], 5]], [ [ - [10, 17], + [100, 107], 0, ['custom', 'bend', 'release', 'bendrelease', 'hold', 'prebend', 'prebendbend', 'prebendrelease'] ], - [[16], 5] + [[106], 5] ], [ - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ], [ [ - [10, 17], + [100, 107], 0, ['custom', 'bend', 'release', 'bendrelease', 'hold', 'prebend', 'prebendbend', 'prebendrelease'] ], - [[10, 17], 0, ['default', 'gradual', 'fast']], - [[16], 5] + [[100, 107], 0, ['default', 'gradual', 'fast']], + [[106], 5] ] ] ], - ['lf', [[[[16], 0, ['1', '2', '3', '4', '5']]]]], - ['rf', [[[[16], 0, ['1', '2', '3', '4', '5']]]]], + ['lf', [[[[106], 0, ['1', '2', '3', '4', '5']]]]], + ['rf', [[[[106], 0, ['1', '2', '3', '4', '5']]]]], [ 'acc', [ [ [ - [10, 17], + [100, 107], 0, [ 'default', @@ -966,7 +966,7 @@ export class AlphaTex1LanguageDefinitions { ] ] ], - ['slur', [[[[17], 0]], [[[10], 0]]]], + ['slur', [[[[107], 0]], [[[100], 0]]]], ['-', null] ]); } diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts index 83ff9ad8d..0bd5ab102 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTex1LanguageHandler.ts @@ -1181,6 +1181,8 @@ export class AlphaTex1LanguageHandler implements IAlphaTexLanguageImportHandler const instrumentName = (args!.arguments[0] as AlphaTexTextNode).text.toLowerCase(); if (instrumentName === 'percussion') { for (const staff of track.staves) { + // hide tablature by default unless explicitly requested in staff + staff.showTablature = false; importer.applyStaffNoteKind(staff, AlphaTexStaffNoteKind.Articulation); } track.playbackInfo.primaryChannel = SynthConstants.PercussionChannel; diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTexAst.ts b/packages/alphatab/src/importer/alphaTex/AlphaTexAst.ts index 76d7ee857..7d81e078b 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTexAst.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTexAst.ts @@ -14,24 +14,25 @@ export enum AlphaTexNodeType { RParen = 7, Colon = 8, Asterisk = 9, + At = 10, // General Nodes - Ident = 10, - Tag = 11, - Meta = 12, - Arguments = 13, - Props = 14, - Prop = 15, - Number = 16, - String = 17, + Ident = 100, + Tag = 101, + Meta = 102, + Arguments = 103, + Props = 104, + Prop = 105, + Number = 106, + String = 107, // Semantic Nodes - Score = 18, - Bar = 19, - Beat = 20, - Duration = 21, - NoteList = 22, - Note = 23 + Score = 200, + Bar = 201, + Beat = 202, + Duration = 203, + NoteList = 204, + Note = 205 } // @@ -125,11 +126,16 @@ export interface AlphaTexComment { */ export interface AlphaTexTokenNode extends AlphaTexAstNode {} +/** + * @public + */ +export interface IAlphaTexStringSeparatorNode extends IAlphaTexAstNode {} + /** * @record * @public */ -export interface AlphaTexDotTokenNode extends AlphaTexTokenNode { +export interface AlphaTexDotTokenNode extends AlphaTexTokenNode, IAlphaTexStringSeparatorNode { nodeType: AlphaTexNodeType.Dot; } @@ -205,6 +211,14 @@ export interface AlphaTexAsteriskTokenNode extends AlphaTexTokenNode { nodeType: AlphaTexNodeType.Asterisk; } +/** + * @record + * @public + */ +export interface AlphaTexAtTokenNode extends AlphaTexTokenNode, IAlphaTexStringSeparatorNode { + nodeType: AlphaTexNodeType.At; +} + /** * A number literal within alphaTex. Can be a integer or floating point number. * @record @@ -528,9 +542,9 @@ export interface AlphaTexNoteNode extends AlphaTexAstNode { noteValue: IAlphaTexNoteValueNode; /** - * The dot separating the note value and the string for fretted/stringed instruments like guitars. + * The dot or @ separating the note value and the string for fretted/stringed instruments like guitars. */ - noteStringDot?: AlphaTexDotTokenNode; + noteStringSeparator?: IAlphaTexStringSeparatorNode; /** * The string value for fretted/stringed notes like guitars. diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTexLexer.ts b/packages/alphatab/src/importer/alphaTex/AlphaTexLexer.ts index c9b81b313..bef5c22f0 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTexLexer.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTexLexer.ts @@ -2,6 +2,7 @@ type AlphaTexAsteriskTokenNode, type AlphaTexAstNode, type AlphaTexAstNodeLocation, + AlphaTexAtTokenNode, type AlphaTexBackSlashTokenNode, type AlphaTexBraceCloseTokenNode, type AlphaTexBraceOpenTokenNode, @@ -225,6 +226,7 @@ export class AlphaTexLexer { [0x7d /* } */, l => l._token({ nodeType: AlphaTexNodeType.RBrace } as AlphaTexBraceCloseTokenNode)], [0x7c /* | */, l => l._token({ nodeType: AlphaTexNodeType.Pipe } as AlphaTexPipeTokenNode)], [0x2a /* * */, l => l._token({ nodeType: AlphaTexNodeType.Asterisk } as AlphaTexAsteriskTokenNode)], + [0x40 /* @ */, l => l._token({ nodeType: AlphaTexNodeType.At } as AlphaTexAtTokenNode)], [0x5c /* \ */, l => l._metaCommand()], [0x09 /* \t */, l => l._whitespace()], diff --git a/packages/alphatab/src/importer/alphaTex/AlphaTexParser.ts b/packages/alphatab/src/importer/alphaTex/AlphaTexParser.ts index 5d11afbf1..02ff39596 100644 --- a/packages/alphatab/src/importer/alphaTex/AlphaTexParser.ts +++ b/packages/alphatab/src/importer/alphaTex/AlphaTexParser.ts @@ -1,5 +1,6 @@ import { AlphaTex1MetaDataReader } from '@coderline/alphatab/importer/alphaTex/AlphaTex1MetaDataReader'; import { + type AlphaTexArgumentList, type AlphaTexAsteriskTokenNode, type AlphaTexAstNode, type AlphaTexBarNode, @@ -23,7 +24,7 @@ import { type AlphaTexPropertyNode, type AlphaTexScoreNode, type AlphaTexStringLiteral, - type AlphaTexArgumentList + type IAlphaTexStringSeparatorNode } from '@coderline/alphatab/importer/alphaTex/AlphaTexAst'; import { AlphaTexLexer } from '@coderline/alphatab/importer/alphaTex/AlphaTexLexer'; import { @@ -427,13 +428,13 @@ export class AlphaTexParser { start: noteValue.start }; try { - let canHaveString = false; + let canHaveStringDot = false; switch (noteValue.nodeType) { case AlphaTexNodeType.Number: note.noteValue = noteValue as AlphaTexNumberLiteral; this.lexer.advance(); - canHaveString = true; + canHaveStringDot = true; break; case AlphaTexNodeType.String: @@ -442,7 +443,7 @@ export class AlphaTexParser { switch ((note.noteValue as AlphaTexStringLiteral).text) { case 'x': case '-': - canHaveString = true; + canHaveStringDot = true; break; } break; @@ -452,7 +453,7 @@ export class AlphaTexParser { switch ((note.noteValue as AlphaTexIdentifier).text) { case 'x': case '-': - canHaveString = true; + canHaveStringDot = true; break; } break; @@ -465,35 +466,36 @@ export class AlphaTexParser { return undefined; } - if (canHaveString) { - const dot = this.lexer.peekToken(); - if (dot?.nodeType === AlphaTexNodeType.Dot) { - const noteStringDot = dot as AlphaTexDotTokenNode; - this.lexer.advance(); + const separator = this.lexer.peekToken(); + if ( + (canHaveStringDot && separator?.nodeType === AlphaTexNodeType.Dot) || + separator?.nodeType === AlphaTexNodeType.At + ) { + const noteStringSeparator = separator as IAlphaTexStringSeparatorNode; + this.lexer.advance(); - const noteString = this.lexer.peekToken(); - if (!noteString) { - this.unexpectedToken(noteString, [AlphaTexNodeType.Number], true); - return undefined; - } + const noteString = this.lexer.peekToken(); + if (!noteString) { + this.unexpectedToken(noteString, [AlphaTexNodeType.Number], true); + return undefined; + } - if (noteString.nodeType === AlphaTexNodeType.Tag) { - // backwards compatibility with older alphaTex: there was a dot separator - // between the song content and sync points at the end + if (noteString.nodeType === AlphaTexNodeType.Tag) { + // backwards compatibility with older alphaTex: there was a dot separator + // between the song content and sync points at the end - // handle switch to sync points like: 3 4 5 . \sync 1 1 1 - // in this example the numbers are percussion articulations + // handle switch to sync points like: 3 4 5 . \sync 1 1 1 + // in this example the numbers are percussion articulations - // (we can drop the separation dot as it is not part of the AST) - return note; - } else if (noteString.nodeType === AlphaTexNodeType.Number) { - note.noteStringDot = noteStringDot; - note.noteString = noteString as AlphaTexNumberLiteral; - this.lexer.advance(); - } else { - this.unexpectedToken(noteString, [AlphaTexNodeType.Number], true); - return undefined; - } + // (we can drop the separation dot as it is not part of the AST) + return note; + } else if (noteString.nodeType === AlphaTexNodeType.Number) { + note.noteStringSeparator = noteStringSeparator; + note.noteString = noteString as AlphaTexNumberLiteral; + this.lexer.advance(); + } else { + this.unexpectedToken(noteString, [AlphaTexNodeType.Number], true); + return undefined; } } diff --git a/packages/alphatab/src/model/Beat.ts b/packages/alphatab/src/model/Beat.ts index cc17b96d2..1d7b60379 100644 --- a/packages/alphatab/src/model/Beat.ts +++ b/packages/alphatab/src/model/Beat.ts @@ -427,15 +427,15 @@ export class Beat { /** * Gets or sets the chromatic tone value (0–11) of the pitch at which this rest should be displayed. - * A value of -1 means use the default position formula. + * A value of NaN means use the default position formula. */ - public restDisplayTone: number = -1; + public restDisplayTone: number = Number.NaN; /** * Gets or sets the octave at which this rest should be displayed. - * Only relevant when {@link restDisplayTone} is set. -1 means use the default position formula. + * Only relevant when {@link restDisplayTone} is set. NaN means use the default position formula. */ - public restDisplayOctave: number = -1; + public restDisplayOctave: number = Number.NaN; /** * Gets or sets the brush type applied to the notes of this beat. @@ -823,7 +823,7 @@ export class Beat { note.beat = this; note.index = this.notes.length; this.notes.push(note); - if (note.isStringed) { + if (note.string >= 0 ) { this.noteStringLookup.set(note.string, note); } } @@ -832,7 +832,7 @@ export class Beat { const index: number = this.notes.indexOf(note); if (index >= 0) { this.notes.splice(index, 1); - if (note.isStringed) { + if (note.string >= 0) { this.noteStringLookup.delete(note.string); } } diff --git a/packages/alphatab/src/model/Note.ts b/packages/alphatab/src/model/Note.ts index df63ee78e..9eee13391 100644 --- a/packages/alphatab/src/model/Note.ts +++ b/packages/alphatab/src/model/Note.ts @@ -204,21 +204,21 @@ export class Note { } public get isStringed(): boolean { - return this.string >= 0; + return !Number.isNaN(this.string) && !Number.isNaN(this.fret); } /** * Gets or sets the fret on which this note is played on the instrument. * 0 is the nut. */ - public fret: number = -1; + public fret: number = Number.NaN; /** * Gets or sets the string number where the note is placed. * 1 is the lowest string on the guitar and the bottom line on the tablature. * It then increases the the number of strings on available on the track. */ - public string: number = -1; + public string: number = Number.NaN; /** * Gets or sets whether the string number for this note should be shown. @@ -226,21 +226,21 @@ export class Note { public showStringNumber: boolean = false; public get isPiano(): boolean { - return !this.isStringed && this.octave >= 0 && this.tone >= 0; + return !this.isStringed && !Number.isNaN(this.octave) && !Number.isNaN(this.tone); } /** * Gets or sets the octave on which this note is played. */ - public octave: number = -1; + public octave: number = Number.NaN; /** * Gets or sets the tone of this note within the octave. */ - public tone: number = -1; + public tone: number = Number.NaN; public get isPercussion(): boolean { - return !this.isStringed && this.percussionArticulation >= 0; + return !Number.isNaN(this.percussionArticulation); } /** @@ -358,7 +358,7 @@ export class Note { * - 126 Ride (middle) * - 127 Ride (bell) */ - public percussionArticulation: number = -1; + public percussionArticulation: number = Number.NaN; /** * Gets or sets whether this note is visible on the music sheet. @@ -633,7 +633,7 @@ export class Note { } public static getStringTuning(staff: Staff, noteString: number): number { - if (staff.tuning.length > 0) { + if (staff.tuning.length > 0 && noteString >= 0) { return staff.tuning[staff.tuning.length - (noteString - 1) - 1]; } return 0; @@ -1171,7 +1171,7 @@ export class Note { return noteOnString; } } else { - if (note.octave === -1 && note.tone === -1) { + if (Number.isNaN(note.octave) && Number.isNaN(note.tone)) { // if the note has no value (e.g. alphaTex dash tie), we try to find a matching // note on the previous beat by index. if (note.index < previousBeat.notes.length) { diff --git a/packages/alphatab/src/model/PercussionMapper.ts b/packages/alphatab/src/model/PercussionMapper.ts index 6932bbcd2..67c63ef22 100644 --- a/packages/alphatab/src/model/PercussionMapper.ts +++ b/packages/alphatab/src/model/PercussionMapper.ts @@ -1180,7 +1180,7 @@ export class PercussionMapper { return articulationsByOutputNumber.has(articulation.outputMidiNumber) ? articulationsByOutputNumber.get(articulation.outputMidiNumber)!.id - : -1; + : Number.NaN; } private static _instrumentArticulationsByUniqueId: Map | undefined; diff --git a/packages/alphatab/src/model/Staff.ts b/packages/alphatab/src/model/Staff.ts index 9174b4d50..f016c5670 100644 --- a/packages/alphatab/src/model/Staff.ts +++ b/packages/alphatab/src/model/Staff.ts @@ -137,30 +137,29 @@ export class Staff { */ public standardNotationLineCount: number = Staff.DefaultStandardNotationLineCount; - private _filledVoices:Set = new Set([0]); + private _filledVoices: Set = new Set([0]); /** * The indexes of the non-empty voices in this staff.. * @json_ignore */ - public get filledVoices():Set { + public get filledVoices(): Set { return this._filledVoices; } public finish(settings: Settings, sharedDataBag: Map | null = null): void { + this.stringTuning.finish(); if (this.isPercussion) { - this.stringTuning.tunings = []; - this.showTablature = false; this.displayTranspositionPitch = 0; + this.stringTuning.tunings = [0, 0, 0, 0, 0, 0]; } - this.stringTuning.finish(); - if(this.stringTuning.tunings.length === 0){ + if (this.stringTuning.tunings.length === 0) { this.showTablature = false; } for (let i: number = 0, j: number = this.bars.length; i < j; i++) { this.bars[i].finish(settings, sharedDataBag); - for(const v of this.bars[i].filledVoices) { + for (const v of this.bars[i].filledVoices) { this._filledVoices.add(v); } } diff --git a/packages/alphatab/src/rendering/TabBarRendererFactory.ts b/packages/alphatab/src/rendering/TabBarRendererFactory.ts index 54ca6d3c7..d0ecaca1d 100644 --- a/packages/alphatab/src/rendering/TabBarRendererFactory.ts +++ b/packages/alphatab/src/rendering/TabBarRendererFactory.ts @@ -2,7 +2,7 @@ import type { Bar } from '@coderline/alphatab/model/Bar'; import type { Staff } from '@coderline/alphatab/model/Staff'; import type { Track } from '@coderline/alphatab/model/Track'; import type { BarRendererBase } from '@coderline/alphatab/rendering/BarRendererBase'; -import { BarRendererFactory, type EffectBandInfo } from '@coderline/alphatab/rendering/BarRendererFactory'; +import { BarRendererFactory } from '@coderline/alphatab/rendering/BarRendererFactory'; import type { ScoreRenderer } from '@coderline/alphatab/rendering/ScoreRenderer'; import { TabBarRenderer } from '@coderline/alphatab/rendering/TabBarRenderer'; @@ -19,11 +19,6 @@ export class TabBarRendererFactory extends BarRendererFactory { return 1; } - public constructor(effectBands: EffectBandInfo[]) { - super(effectBands); - this.hideOnPercussionTrack = true; - } - public override canCreate(track: Track, staff: Staff): boolean { return staff.showTablature && staff.tuning.length > 0 && super.canCreate(track, staff); } diff --git a/packages/alphatab/src/rendering/glyphs/NoteNumberGlyph.ts b/packages/alphatab/src/rendering/glyphs/NoteNumberGlyph.ts index d97617b2b..657c2a182 100644 --- a/packages/alphatab/src/rendering/glyphs/NoteNumberGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/NoteNumberGlyph.ts @@ -3,6 +3,7 @@ import type { Font } from '@coderline/alphatab/model/Font'; import { HarmonicType } from '@coderline/alphatab/model/HarmonicType'; import { ModelUtils } from '@coderline/alphatab/model/ModelUtils'; import { type Note, NoteSubElement } from '@coderline/alphatab/model/Note'; +import { PercussionMapper } from '@coderline/alphatab/model/PercussionMapper'; import { NotationElement, NotationMode } from '@coderline/alphatab/NotationSettings'; import type { ICanvas } from '@coderline/alphatab/platform/ICanvas'; import { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; @@ -43,10 +44,21 @@ export class NoteNumberGlyph extends Glyph { public override doLayout(): void { const n: Note = this._note; - let fret: number = n.fret - n.beat.voice.bar.staff.transpositionPitch; - if (n.harmonicType === HarmonicType.Natural && n.harmonicValue !== 0) { - fret = n.harmonicValue - n.beat.voice.bar.staff.transpositionPitch; + let fret: number; + if (n.isStringed) { + fret = n.fret - n.beat.voice.bar.staff.transpositionPitch; + if (n.harmonicType === HarmonicType.Natural && n.harmonicValue !== 0) { + fret = n.harmonicValue - n.beat.voice.bar.staff.transpositionPitch; + } + } else { + const articulation = PercussionMapper.getArticulation(n); + if (articulation) { + fret = articulation.id; + } else { + fret = n.percussionArticulation; + } } + if (!n.isTieDestination) { this._noteString = n.isDead ? 'x' : fret.toString(); if (n.isGhost) { diff --git a/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts index 28d513072..59da5271a 100644 --- a/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts @@ -306,7 +306,7 @@ export class ScoreBeatGlyph extends BeatOnNoteGlyphBase { const lineCount = this.renderer.bar.staff.standardNotationLineCount; let steps: number; - if (beat.restDisplayTone !== -1 && beat.restDisplayOctave !== -1) { + if (!Number.isNaN(beat.restDisplayTone) && !Number.isNaN(beat.restDisplayOctave)) { // Per-beat override: same step as a note at that pitch. SMuFL rest glyphs use the same // baseline convention as note heads, so no further adjustment is applied. steps = AccidentalHelper.calculateRestDisplaySteps(sr.bar, beat.restDisplayTone, beat.restDisplayOctave); diff --git a/packages/alphatab/test-data/guitarpro7/drum-custom-lines.gp b/packages/alphatab/test-data/guitarpro7/drum-custom-lines.gp new file mode 100644 index 000000000..9f0c25070 Binary files /dev/null and b/packages/alphatab/test-data/guitarpro7/drum-custom-lines.gp differ diff --git a/packages/alphatab/test-data/guitarpro7/drum-tabs.gp b/packages/alphatab/test-data/guitarpro7/drum-tabs.gp new file mode 100644 index 000000000..b055181dd Binary files /dev/null and b/packages/alphatab/test-data/guitarpro7/drum-tabs.gp differ diff --git a/packages/alphatab/test/PrettyFormat.ts b/packages/alphatab/test/PrettyFormat.ts index f96c711b8..aa84ce69f 100644 --- a/packages/alphatab/test/PrettyFormat.ts +++ b/packages/alphatab/test/PrettyFormat.ts @@ -633,8 +633,8 @@ export class AlphaTexAstNodePlugin implements PrettyFormatNewPlugin { if (note.noteValue) { children.push(['noteValue', note.noteValue]); } - if (note.noteStringDot) { - children.push(['noteStringDot', note.noteStringDot]); + if (note.noteStringSeparator) { + children.push(['noteStringSeparator', note.noteStringSeparator]); } if (note.noteString) { children.push(['noteString', note.noteString]); @@ -873,7 +873,7 @@ export class ScoreSerializerPlugin implements PrettyFormatNewPlugin { isEqual = (v as string) === (dv as string); break; case 'number': - isEqual = (v as number) === (dv as number); + isEqual = (v as number) === (dv as number) || (Number.isNaN(v as number) && Number.isNaN(dv as number)); break; case 'bigint': isEqual = (v as bigint) === (dv as bigint); diff --git a/packages/alphatab/test/importer/AlphaTexLexer.test.ts b/packages/alphatab/test/importer/AlphaTexLexer.test.ts index ace725e35..2bdd4dde7 100644 --- a/packages/alphatab/test/importer/AlphaTexLexer.test.ts +++ b/packages/alphatab/test/importer/AlphaTexLexer.test.ts @@ -73,6 +73,7 @@ describe('AlphaTexLexerTest', () => { lexerTest(`}`); lexerTest(`|`); lexerTest(`*`); + lexerTest(`@`); }); it('meta-command', () => { diff --git a/packages/alphatab/test/importer/Gp7Importer.test.ts b/packages/alphatab/test/importer/Gp7Importer.test.ts index e81669f19..e7a551bbd 100644 --- a/packages/alphatab/test/importer/Gp7Importer.test.ts +++ b/packages/alphatab/test/importer/Gp7Importer.test.ts @@ -19,6 +19,7 @@ import { GpImporterTestHelper } from 'test/importer/GpImporterTestHelper'; import { TestPlatform } from 'test/TestPlatform'; import { AutomationType } from '@coderline/alphatab/model/Automation'; import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection'; +import { PercussionMapper } from '@coderline/alphatab/model/PercussionMapper'; describe('Gp7ImporterTest', () => { async function prepareImporterWithFile(name: string): Promise { @@ -974,21 +975,15 @@ describe('Gp7ImporterTest', () => { expect(score.tracks[0].staves[0].bars[1].voices[0].beats[3].preferredBeamDirection).toBe(BeamDirection.Up); // break - expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].beamingMode).toBe( - BeatBeamingMode.ForceSplitToNext - ); + expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].beamingMode).toBe(BeatBeamingMode.ForceSplitToNext); expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].invertBeamDirection).toBe(false); expect(score.tracks[0].staves[0].bars[2].voices[0].beats[0].preferredBeamDirection).toBe(BeamDirection.Up); - expect(score.tracks[0].staves[0].bars[2].voices[0].beats[1].beamingMode).toBe( - BeatBeamingMode.ForceSplitToNext - ); + expect(score.tracks[0].staves[0].bars[2].voices[0].beats[1].beamingMode).toBe(BeatBeamingMode.ForceSplitToNext); expect(score.tracks[0].staves[0].bars[2].voices[0].beats[1].invertBeamDirection).toBe(false); expect(score.tracks[0].staves[0].bars[2].voices[0].beats[1].preferredBeamDirection).toBe(BeamDirection.Up); - expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].beamingMode).toBe( - BeatBeamingMode.ForceSplitToNext - ); + expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].beamingMode).toBe(BeatBeamingMode.ForceSplitToNext); expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].invertBeamDirection).toBe(false); expect(score.tracks[0].staves[0].bars[2].voices[0].beats[2].preferredBeamDirection).toBe(BeamDirection.Up); @@ -1014,13 +1009,60 @@ describe('Gp7ImporterTest', () => { // invert to down expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].beamingMode).toBe(BeatBeamingMode.Auto); expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].invertBeamDirection).toBe(false); - expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].preferredBeamDirection).toBe( - BeamDirection.Down - ); + expect(score.tracks[0].staves[0].bars[4].voices[0].beats[0].preferredBeamDirection).toBe(BeamDirection.Down); // invert to up expect(score.tracks[0].staves[0].bars[5].voices[0].beats[0].beamingMode).toBe(BeatBeamingMode.Auto); expect(score.tracks[0].staves[0].bars[5].voices[0].beats[0].invertBeamDirection).toBe(false); expect(score.tracks[0].staves[0].bars[5].voices[0].beats[0].preferredBeamDirection).toBe(BeamDirection.Up); }); + + it('drum-tabs-preserves-percussion-tab-data', async () => { + const reader = await prepareImporterWithFile('guitarpro7/drum-tabs.gp'); + const score: Score = reader.readScore(); + + const staff = score.tracks[0].staves[0]; + expect(staff.isPercussion).toBe(true); + expect(staff.tuning.length).toBe(6); + expect(staff.tuning.some((t: number) => t !== 0)).toBe(false); + + const beats = staff.bars[0].voices[0].beats; + expect(beats.length).toBe(16); + + const articulationIds = [29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45]; + const articulationStrings = [1, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 2, 5, 2, 5, 2]; + let noteIndex = 0; + for (const beat of beats) { + for (const note of beat.notes) { + expect(note.isPercussion).toBe(true); + expect(note.isStringed).toBe(false); + expect(note.string).toBeGreaterThanOrEqual(1); + expect(note.string).toBe(articulationStrings[noteIndex]); + expect(PercussionMapper.getArticulation(note)!.id).toBe(articulationIds[noteIndex]); + noteIndex++; + } + } + }); + + it('drum-custom-lines-preserves-string-assignments', async () => { + const reader = await prepareImporterWithFile('guitarpro7/drum-custom-lines.gp'); + const score: Score = reader.readScore(); + + const staff = score.tracks[0].staves[0]; + expect(staff.isPercussion).toBe(true); + expect(staff.showTablature).toBe(true); + expect(staff.tuning.length).toBe(6); + + const beats = staff.bars[0].voices[0].beats; + expect(beats.length).toBe(4); + + expect(beats[0].notes[0].string).toBe(5); + expect(PercussionMapper.getArticulation(beats[0].notes[0])!.id).toBe(36); + expect(beats[1].notes[0].string).toBe(4); + expect(PercussionMapper.getArticulation(beats[1].notes[0])!.id).toBe(36); + expect(beats[2].notes[0].string).toBe(3); + expect(PercussionMapper.getArticulation(beats[2].notes[0])!.id).toBe(36); + expect(beats[3].notes[0].string).toBe(2); + expect(PercussionMapper.getArticulation(beats[3].notes[0])!.id).toBe(36); + }); }); diff --git a/packages/alphatab/test/importer/__snapshots__/AlphaTexLexer.test.ts.snap b/packages/alphatab/test/importer/__snapshots__/AlphaTexLexer.test.ts.snap index b4f8b8d62..f08bf1d9d 100644 --- a/packages/alphatab/test/importer/__snapshots__/AlphaTexLexer.test.ts.snap +++ b/packages/alphatab/test/importer/__snapshots__/AlphaTexLexer.test.ts.snap @@ -48,6 +48,12 @@ exports[`AlphaTexLexerTest > basic-tokens 8`] = ` ] `; +exports[`AlphaTexLexerTest > basic-tokens 9`] = ` +[ + At (1,1) -> (1,2), +] +`; + exports[`AlphaTexLexerTest > errors > at001 1`] = `[]`; exports[`AlphaTexLexerTest > errors > at001 2`] = ` diff --git a/packages/alphatab/test/importer/__snapshots__/AlphaTexParameter.test.ts.snap b/packages/alphatab/test/importer/__snapshots__/AlphaTexParameter.test.ts.snap index 395d10e4a..228823b3d 100644 --- a/packages/alphatab/test/importer/__snapshots__/AlphaTexParameter.test.ts.snap +++ b/packages/alphatab/test/importer/__snapshots__/AlphaTexParameter.test.ts.snap @@ -2250,7 +2250,7 @@ Score (1,1) -> (1,10) { notes: [ Note (1,7) -> (1,10) { noteValue: Number "1" (1,7) -> (1,8), - noteStringDot: Dot (1,8) -> (1,9), + noteStringSeparator: Dot (1,8) -> (1,9), noteString: Number "1" (1,9) -> (1,10), }, ], @@ -2600,7 +2600,7 @@ Score (1,1) -> (1,10) { notes: [ Note (1,7) -> (1,10) { noteValue: Number "3" (1,7) -> (1,8), - noteStringDot: Dot (1,8) -> (1,9), + noteStringSeparator: Dot (1,8) -> (1,9), noteString: Number "3" (1,9) -> (1,10), }, ], @@ -3382,7 +3382,7 @@ Score (1,1) -> (1,10) { notes: [ Note (1,7) -> (1,10) { noteValue: Number "3" (1,7) -> (1,8), - noteStringDot: Dot (1,8) -> (1,9), + noteStringSeparator: Dot (1,8) -> (1,9), noteString: Number "3" (1,9) -> (1,10), }, ], diff --git a/packages/alphatab/test/importer/__snapshots__/AlphaTexParser.test.ts.snap b/packages/alphatab/test/importer/__snapshots__/AlphaTexParser.test.ts.snap index cc17a13f8..9fda0aa90 100644 --- a/packages/alphatab/test/importer/__snapshots__/AlphaTexParser.test.ts.snap +++ b/packages/alphatab/test/importer/__snapshots__/AlphaTexParser.test.ts.snap @@ -37,7 +37,7 @@ Score (1,1) -> (1,19) { notes: [ Note (1,12) -> (1,15) { noteValue: Number "3" (1,12) -> (1,13), - noteStringDot: Dot (1,13) -> (1,14), + noteStringSeparator: Dot (1,13) -> (1,14), noteString: Number "3" (1,14) -> (1,15), }, ], @@ -48,7 +48,7 @@ Score (1,1) -> (1,19) { notes: [ Note (1,16) -> (1,19) { noteValue: Number "3" (1,16) -> (1,17), - noteStringDot: Dot (1,17) -> (1,18), + noteStringSeparator: Dot (1,17) -> (1,18), noteString: Number "4" (1,18) -> (1,19), }, ], @@ -662,7 +662,7 @@ Score (1,1) -> (1,8) { notes: [ Note (1,2) -> (1,5) { noteValue: Number "3" (1,2) -> (1,3), - noteStringDot: Dot (1,3) -> (1,4), + noteStringSeparator: Dot (1,3) -> (1,4), noteString: Number "3" (1,4) -> (1,5), }, ], @@ -704,7 +704,7 @@ Score (1,1) -> (1,10) { notes: [ Note (1,2) -> (1,5) { noteValue: Number "3" (1,2) -> (1,3), - noteStringDot: Dot (1,3) -> (1,4), + noteStringSeparator: Dot (1,3) -> (1,4), noteString: Number "3" (1,4) -> (1,5), }, ], @@ -3623,7 +3623,7 @@ Score (1,1) -> (1,8) { notes: [ Note (1,1) -> (1,4) { noteValue: Number "3" (1,1) -> (1,2), - noteStringDot: Dot (1,2) -> (1,3), + noteStringSeparator: Dot (1,2) -> (1,3), noteString: Number "3" (1,3) -> (1,4), }, ], @@ -3634,7 +3634,7 @@ Score (1,1) -> (1,8) { notes: [ Note (1,5) -> (1,8) { noteValue: Number "4" (1,5) -> (1,6), - noteStringDot: Dot (1,6) -> (1,7), + noteStringSeparator: Dot (1,6) -> (1,7), noteString: Number "2" (1,7) -> (1,8), }, ], @@ -3664,7 +3664,7 @@ Score (1,1) -> (1,26) { notes: [ Note (1,4) -> (1,7) { noteValue: Number "3" (1,4) -> (1,5), - noteStringDot: Dot (1,5) -> (1,6), + noteStringSeparator: Dot (1,5) -> (1,6), noteString: Number "3" (1,6) -> (1,7), }, ], @@ -3725,7 +3725,7 @@ Score (1,1) -> (1,12) { notes: [ Note (1,1) -> (1,4) { noteValue: Number "3" (1,1) -> (1,2), - noteStringDot: Dot (1,2) -> (1,3), + noteStringSeparator: Dot (1,2) -> (1,3), noteString: Number "3" (1,3) -> (1,4), }, ], @@ -3738,7 +3738,7 @@ Score (1,1) -> (1,12) { notes: [ Note (1,7) -> (1,10) { noteValue: Number "4" (1,7) -> (1,8), - noteStringDot: Dot (1,8) -> (1,9), + noteStringSeparator: Dot (1,8) -> (1,9), noteString: Number "2" (1,9) -> (1,10), }, ], @@ -3770,7 +3770,7 @@ Score (1,1) -> (1,14) { notes: [ Note (1,4) -> (1,7) { noteValue: Number "3" (1,4) -> (1,5), - noteStringDot: Dot (1,5) -> (1,6), + noteStringSeparator: Dot (1,5) -> (1,6), noteString: Number "3" (1,6) -> (1,7), }, ], @@ -3785,7 +3785,7 @@ Score (1,1) -> (1,14) { notes: [ Note (1,11) -> (1,14) { noteValue: Number "4" (1,11) -> (1,12), - noteStringDot: Dot (1,12) -> (1,13), + noteStringSeparator: Dot (1,12) -> (1,13), noteString: Number "2" (1,13) -> (1,14), }, ], @@ -3811,7 +3811,7 @@ Score (1,1) -> (1,18) { notes: [ Note (1,1) -> (1,4) { noteValue: Number "3" (1,1) -> (1,2), - noteStringDot: Dot (1,2) -> (1,3), + noteStringSeparator: Dot (1,2) -> (1,3), noteString: Number "3" (1,3) -> (1,4), }, ], @@ -3829,7 +3829,7 @@ Score (1,1) -> (1,18) { notes: [ Note (1,10) -> (1,13) { noteValue: Number "4" (1,10) -> (1,11), - noteStringDot: Dot (1,11) -> (1,12), + noteStringSeparator: Dot (1,11) -> (1,12), noteString: Number "2" (1,12) -> (1,13), }, ], @@ -3862,7 +3862,7 @@ Score (1,1) -> (1,31) { notes: [ Note (1,1) -> (1,4) { noteValue: Number "3" (1,1) -> (1,2), - noteStringDot: Dot (1,2) -> (1,3), + noteStringSeparator: Dot (1,2) -> (1,3), noteString: Number "3" (1,3) -> (1,4), }, ], @@ -3887,7 +3887,7 @@ Score (1,1) -> (1,31) { notes: [ Note (1,13) -> (1,16) { noteValue: Number "4" (1,13) -> (1,14), - noteStringDot: Dot (1,14) -> (1,15), + noteStringSeparator: Dot (1,14) -> (1,15), noteString: Number "2" (1,15) -> (1,16), }, ], @@ -3946,7 +3946,7 @@ Score (1,1) -> (1,12) { notes: [ Note (1,1) -> (1,4) { noteValue: Number "3" (1,1) -> (1,2), - noteStringDot: Dot (1,2) -> (1,3), + noteStringSeparator: Dot (1,2) -> (1,3), noteString: Number "3" (1,3) -> (1,4), }, ], @@ -3959,7 +3959,7 @@ Score (1,1) -> (1,12) { notes: [ Note (1,7) -> (1,10) { noteValue: Number "4" (1,7) -> (1,8), - noteStringDot: Dot (1,8) -> (1,9), + noteStringSeparator: Dot (1,8) -> (1,9), noteString: Number "2" (1,9) -> (1,10), }, ], @@ -3987,7 +3987,7 @@ Score (1,1) -> (1,37) { notes: [ Note (1,1) -> (1,4) { noteValue: Number "3" (1,1) -> (1,2), - noteStringDot: Dot (1,2) -> (1,3), + noteStringSeparator: Dot (1,2) -> (1,3), noteString: Number "3" (1,3) -> (1,4), }, ], @@ -4005,7 +4005,7 @@ Score (1,1) -> (1,37) { notes: [ Note (1,9) -> (1,14) { noteValue: Number "3" (1,9) -> (1,10), - noteStringDot: Dot (1,11) -> (1,12), + noteStringSeparator: Dot (1,11) -> (1,12), noteString: Number "3" (1,13) -> (1,14), }, ], @@ -4023,7 +4023,7 @@ Score (1,1) -> (1,37) { notes: [ Note (1,21) -> (1,24) { noteValue: Number "3" (1,21) -> (1,22), - noteStringDot: Dot (1,22) -> (1,23), + noteStringSeparator: Dot (1,22) -> (1,23), noteString: Number "3" (1,23) -> (1,24), }, ], @@ -4041,7 +4041,7 @@ Score (1,1) -> (1,37) { notes: [ Note (1,30) -> (1,35) { noteValue: Number "3" (1,30) -> (1,31), - noteStringDot: Dot (1,32) -> (1,33), + noteStringSeparator: Dot (1,32) -> (1,33), noteString: Number "3" (1,34) -> (1,35), }, ], @@ -4408,12 +4408,12 @@ Score (1,1) -> (1,20) { notes: [ Note (1,2) -> (1,5) { noteValue: Number "3" (1,2) -> (1,3), - noteStringDot: Dot (1,3) -> (1,4), + noteStringSeparator: Dot (1,3) -> (1,4), noteString: Number "3" (1,4) -> (1,5), }, Note (1,6) -> (1,9) { noteValue: Number "4" (1,6) -> (1,7), - noteStringDot: Dot (1,7) -> (1,8), + noteStringSeparator: Dot (1,7) -> (1,8), noteString: Number "2" (1,8) -> (1,9), }, ], @@ -4426,12 +4426,12 @@ Score (1,1) -> (1,20) { notes: [ Note (1,12) -> (1,15) { noteValue: Number "1" (1,12) -> (1,13), - noteStringDot: Dot (1,13) -> (1,14), + noteStringSeparator: Dot (1,13) -> (1,14), noteString: Number "2" (1,14) -> (1,15), }, Note (1,16) -> (1,19) { noteValue: Number "6" (1,16) -> (1,17), - noteStringDot: Dot (1,17) -> (1,18), + noteStringSeparator: Dot (1,17) -> (1,18), noteString: Number "1" (1,18) -> (1,19), }, ], @@ -4463,12 +4463,12 @@ Score (1,1) -> (1,32) { notes: [ Note (1,5) -> (1,8) { noteValue: Number "3" (1,5) -> (1,6), - noteStringDot: Dot (1,6) -> (1,7), + noteStringSeparator: Dot (1,6) -> (1,7), noteString: Number "3" (1,7) -> (1,8), }, Note (1,9) -> (1,12) { noteValue: Number "4" (1,9) -> (1,10), - noteStringDot: Dot (1,10) -> (1,11), + noteStringSeparator: Dot (1,10) -> (1,11), noteString: Number "2" (1,11) -> (1,12), }, ], @@ -4531,12 +4531,12 @@ Score (1,1) -> (1,24) { notes: [ Note (1,2) -> (1,5) { noteValue: Number "3" (1,2) -> (1,3), - noteStringDot: Dot (1,3) -> (1,4), + noteStringSeparator: Dot (1,3) -> (1,4), noteString: Number "3" (1,4) -> (1,5), }, Note (1,6) -> (1,9) { noteValue: Number "4" (1,6) -> (1,7), - noteStringDot: Dot (1,7) -> (1,8), + noteStringSeparator: Dot (1,7) -> (1,8), noteString: Number "2" (1,8) -> (1,9), }, ], @@ -4551,12 +4551,12 @@ Score (1,1) -> (1,24) { notes: [ Note (1,14) -> (1,17) { noteValue: Number "1" (1,14) -> (1,15), - noteStringDot: Dot (1,15) -> (1,16), + noteStringSeparator: Dot (1,15) -> (1,16), noteString: Number "2" (1,16) -> (1,17), }, Note (1,18) -> (1,21) { noteValue: Number "6" (1,18) -> (1,19), - noteStringDot: Dot (1,19) -> (1,20), + noteStringSeparator: Dot (1,19) -> (1,20), noteString: Number "1" (1,20) -> (1,21), }, ], @@ -4590,12 +4590,12 @@ Score (1,1) -> (1,26) { notes: [ Note (1,5) -> (1,8) { noteValue: Number "3" (1,5) -> (1,6), - noteStringDot: Dot (1,6) -> (1,7), + noteStringSeparator: Dot (1,6) -> (1,7), noteString: Number "3" (1,7) -> (1,8), }, Note (1,9) -> (1,12) { noteValue: Number "4" (1,9) -> (1,10), - noteStringDot: Dot (1,10) -> (1,11), + noteStringSeparator: Dot (1,10) -> (1,11), noteString: Number "2" (1,11) -> (1,12), }, ], @@ -4612,12 +4612,12 @@ Score (1,1) -> (1,26) { notes: [ Note (1,18) -> (1,21) { noteValue: Number "1" (1,18) -> (1,19), - noteStringDot: Dot (1,19) -> (1,20), + noteStringSeparator: Dot (1,19) -> (1,20), noteString: Number "2" (1,20) -> (1,21), }, Note (1,22) -> (1,25) { noteValue: Number "6" (1,22) -> (1,23), - noteStringDot: Dot (1,23) -> (1,24), + noteStringSeparator: Dot (1,23) -> (1,24), noteString: Number "1" (1,24) -> (1,25), }, ], @@ -4645,12 +4645,12 @@ Score (1,1) -> (1,30) { notes: [ Note (1,2) -> (1,5) { noteValue: Number "3" (1,2) -> (1,3), - noteStringDot: Dot (1,3) -> (1,4), + noteStringSeparator: Dot (1,3) -> (1,4), noteString: Number "3" (1,4) -> (1,5), }, Note (1,6) -> (1,9) { noteValue: Number "4" (1,6) -> (1,7), - noteStringDot: Dot (1,7) -> (1,8), + noteStringSeparator: Dot (1,7) -> (1,8), noteString: Number "2" (1,8) -> (1,9), }, ], @@ -4670,12 +4670,12 @@ Score (1,1) -> (1,30) { notes: [ Note (1,17) -> (1,20) { noteValue: Number "1" (1,17) -> (1,18), - noteStringDot: Dot (1,18) -> (1,19), + noteStringSeparator: Dot (1,18) -> (1,19), noteString: Number "2" (1,19) -> (1,20), }, Note (1,21) -> (1,24) { noteValue: Number "6" (1,21) -> (1,22), - noteStringDot: Dot (1,22) -> (1,23), + noteStringSeparator: Dot (1,22) -> (1,23), noteString: Number "1" (1,23) -> (1,24), }, ], @@ -4710,12 +4710,12 @@ Score (1,1) -> (1,43) { notes: [ Note (1,2) -> (1,5) { noteValue: Number "3" (1,2) -> (1,3), - noteStringDot: Dot (1,3) -> (1,4), + noteStringSeparator: Dot (1,3) -> (1,4), noteString: Number "3" (1,4) -> (1,5), }, Note (1,6) -> (1,9) { noteValue: Number "4" (1,6) -> (1,7), - noteStringDot: Dot (1,7) -> (1,8), + noteStringSeparator: Dot (1,7) -> (1,8), noteString: Number "2" (1,8) -> (1,9), }, ], @@ -4742,12 +4742,12 @@ Score (1,1) -> (1,43) { notes: [ Note (1,20) -> (1,23) { noteValue: Number "1" (1,20) -> (1,21), - noteStringDot: Dot (1,21) -> (1,22), + noteStringSeparator: Dot (1,21) -> (1,22), noteString: Number "2" (1,22) -> (1,23), }, Note (1,24) -> (1,27) { noteValue: Number "6" (1,24) -> (1,25), - noteStringDot: Dot (1,25) -> (1,26), + noteStringSeparator: Dot (1,25) -> (1,26), noteString: Number "1" (1,26) -> (1,27), }, ], @@ -4808,12 +4808,12 @@ Score (1,1) -> (1,24) { notes: [ Note (1,2) -> (1,5) { noteValue: Number "3" (1,2) -> (1,3), - noteStringDot: Dot (1,3) -> (1,4), + noteStringSeparator: Dot (1,3) -> (1,4), noteString: Number "3" (1,4) -> (1,5), }, Note (1,6) -> (1,9) { noteValue: Number "4" (1,6) -> (1,7), - noteStringDot: Dot (1,7) -> (1,8), + noteStringSeparator: Dot (1,7) -> (1,8), noteString: Number "2" (1,8) -> (1,9), }, ], @@ -4828,12 +4828,12 @@ Score (1,1) -> (1,24) { notes: [ Note (1,14) -> (1,17) { noteValue: Number "1" (1,14) -> (1,15), - noteStringDot: Dot (1,15) -> (1,16), + noteStringSeparator: Dot (1,15) -> (1,16), noteString: Number "2" (1,16) -> (1,17), }, Note (1,18) -> (1,21) { noteValue: Number "6" (1,18) -> (1,19), - noteStringDot: Dot (1,19) -> (1,20), + noteStringSeparator: Dot (1,19) -> (1,20), noteString: Number "1" (1,20) -> (1,21), }, ], @@ -4863,12 +4863,12 @@ Score (1,1) -> (5,18) { notes: [ Note (3,17) -> (3,20) { noteValue: Number "3" (3,17) -> (3,18), - noteStringDot: Dot (3,18) -> (3,19), + noteStringSeparator: Dot (3,18) -> (3,19), noteString: Number "3" (3,19) -> (3,20), }, Note (4,17) -> (4,22) { noteValue: Number "3" (4,17) -> (4,18), - noteStringDot: Dot (4,19) -> (4,20), + noteStringSeparator: Dot (4,19) -> (4,20), noteString: Number "3" (4,21) -> (4,22), }, ], @@ -6331,12 +6331,12 @@ Score (1,1) -> (1,28) { notes: [ Note (1,18) -> (1,21) { noteValue: Number "3" (1,18) -> (1,19), - noteStringDot: Dot (1,19) -> (1,20), + noteStringSeparator: Dot (1,19) -> (1,20), noteString: Number "3" (1,20) -> (1,21), }, Note (1,22) -> (1,25) { noteValue: Number "3" (1,22) -> (1,23), - noteStringDot: Dot (1,23) -> (1,24), + noteStringSeparator: Dot (1,23) -> (1,24), noteString: Number "3" (1,24) -> (1,25), }, ], @@ -6513,7 +6513,7 @@ Score (1,1) -> (1,21) { notes: [ Note (1,4) -> (1,7) { noteValue: Number "3" (1,4) -> (1,5), - noteStringDot: Dot (1,5) -> (1,6), + noteStringSeparator: Dot (1,5) -> (1,6), noteString: Number "3" (1,6) -> (1,7), }, ], diff --git a/packages/alphatab/test/model/PercussionTablature.test.ts b/packages/alphatab/test/model/PercussionTablature.test.ts new file mode 100644 index 000000000..98914d49c --- /dev/null +++ b/packages/alphatab/test/model/PercussionTablature.test.ts @@ -0,0 +1,129 @@ +import { Note } from '@coderline/alphatab/model/Note'; +import { Staff } from '@coderline/alphatab/model/Staff'; +import { Track } from '@coderline/alphatab/model/Track'; +import { Tuning } from '@coderline/alphatab/model/Tuning'; +import { TabBarRendererFactory } from '@coderline/alphatab/rendering/TabBarRendererFactory'; +import { Settings } from '@coderline/alphatab/Settings'; +import { describe, expect, it } from 'vitest'; + +describe('PercussionTablature', () => { + describe('Note.isPercussion', () => { + it('returns true when percussionArticulation is set regardless of string', () => { + const note = new Note(); + note.percussionArticulation = 36; + note.string = 6; + note.fret = 36; + + expect(note.isPercussion).toBe(true); + expect(note.isStringed).toBe(true); + }); + + it('returns true when percussionArticulation is set without string', () => { + const note = new Note(); + note.percussionArticulation = 38; + + expect(note.isPercussion).toBe(true); + expect(note.isStringed).toBe(false); + }); + + it('returns false when percussionArticulation is not set', () => { + const note = new Note(); + note.string = 1; + note.fret = 5; + + expect(note.isPercussion).toBe(false); + expect(note.isStringed).toBe(true); + }); + }); + + describe('Staff.finish', () => { + it('preserves showTablature and tuning for percussion with virtual tuning', () => { + const staff = new Staff(); + staff.isPercussion = true; + staff.showTablature = true; + staff.stringTuning = new Tuning('', [0, 0, 0, 0, 0, 0], false); + + staff.finish(new Settings()); + + expect(staff.showTablature).toBe(true); + expect(staff.tuning.length).toBe(6); + expect(staff.displayTranspositionPitch).toBe(0); + }); + + it('preserves showTablature for percussion without tuning', () => { + const staff = new Staff(); + staff.isPercussion = true; + staff.showTablature = true; + staff.stringTuning = new Tuning('', [], false); + + staff.finish(new Settings()); + + expect(staff.showTablature).toBe(true); + expect(staff.tuning.length).toBe(6); + }); + + it('resets displayTranspositionPitch for percussion', () => { + const staff = new Staff(); + staff.isPercussion = true; + staff.displayTranspositionPitch = 12; + staff.stringTuning = new Tuning('', [0, 0, 0, 0, 0, 0], false); + + staff.finish(new Settings()); + + expect(staff.displayTranspositionPitch).toBe(0); + }); + + it('preserves showTablature for non-percussion with tuning', () => { + const staff = new Staff(); + staff.isPercussion = false; + staff.showTablature = true; + staff.stringTuning = new Tuning('', [64, 59, 55, 50, 45, 40], false); + + staff.finish(new Settings()); + + expect(staff.showTablature).toBe(true); + expect(staff.tuning.length).toBe(6); + }); + }); + + describe('TabBarRendererFactory.canCreate', () => { + function createStaff(isPercussion: boolean, showTablature: boolean, tuning: number[]): [Track, Staff] { + const track = new Track(); + const staff = new Staff(); + staff.isPercussion = isPercussion; + staff.showTablature = showTablature; + staff.stringTuning = new Tuning('', tuning, false); + staff.track = track; + track.staves.push(staff); + return [track, staff]; + } + + it('allows creation for percussion staff with virtual tuning and showTablature', () => { + const factory = new TabBarRendererFactory([]); + const [track, staff] = createStaff(true, true, [0, 0, 0, 0, 0, 0]); + + expect(factory.canCreate(track, staff)).toBe(true); + }); + + it('rejects percussion staff when showTablature is false', () => { + const factory = new TabBarRendererFactory([]); + const [track, staff] = createStaff(true, false, [0, 0, 0, 0, 0, 0]); + + expect(factory.canCreate(track, staff)).toBe(false); + }); + + it('rejects percussion staff without tuning', () => { + const factory = new TabBarRendererFactory([]); + const [track, staff] = createStaff(true, true, []); + + expect(factory.canCreate(track, staff)).toBe(false); + }); + + it('allows creation for regular guitar staff', () => { + const factory = new TabBarRendererFactory([]); + const [track, staff] = createStaff(false, true, [64, 59, 55, 50, 45, 40]); + + expect(factory.canCreate(track, staff)).toBe(true); + }); + }); +}); diff --git a/packages/alphatex/src/definitions.ts b/packages/alphatex/src/definitions.ts index 14b425f06..9646b46b0 100644 --- a/packages/alphatex/src/definitions.ts +++ b/packages/alphatex/src/definitions.ts @@ -159,6 +159,7 @@ import { instrumentMeta } from '@coderline/alphatab-alphatex/metadata/staff/inst import type { AlphaTexExample, WithDescription, WithSignatures } from '@coderline/alphatab-alphatex/types'; import { barNumberDisplay } from '@coderline/alphatab-alphatex/metadata/bar/barnumberdisplay'; import { beaming } from '@coderline/alphatab-alphatex/metadata/bar/beamingRule'; +import { restDisplayPitch } from '@coderline/alphatab-alphatex/properties/beat/restDisplayPitch'; export const structuralMetaData = metadata(track, staff, voice); export const scoreMetaData = metadata( @@ -295,7 +296,8 @@ export const beatProperties = properties( instrument, bank, fermata, - beam + beam, + restDisplayPitch ); export const noteProperties = properties( diff --git a/packages/alphatex/src/properties/beat/restDisplayPitch.ts b/packages/alphatex/src/properties/beat/restDisplayPitch.ts new file mode 100644 index 000000000..95e61f671 --- /dev/null +++ b/packages/alphatex/src/properties/beat/restDisplayPitch.ts @@ -0,0 +1,24 @@ +import * as alphaTab from '@coderline/alphatab'; +import type { PropertyDefinition } from '@coderline/alphatab-alphatex/types'; + +export const restDisplayPitch: PropertyDefinition = { + property: 'restDisplayPitch', + snippet: 'restDisplayPitch $1$0', + shortDescription: 'Rest Display Pitch', + longDescription: `Define the pitch on which the rest symbol should be placed.`, + signatures: [ + { + parameters: [ + { + name: 'pitch', + shortDescription: 'The note pitch defining the position, like C4', + type: alphaTab.importer.alphaTex.AlphaTexNodeType.Ident, + parseMode: alphaTab.importer.alphaTex.ArgumentListParseTypesMode.Required + } + ] + } + ], + examples: ` + r.4{ restDisplayPitch C4 } C4 + ` +}; diff --git a/packages/kotlin/src/android/src/main/java/alphaTab/collections/DoubleList.kt b/packages/kotlin/src/android/src/main/java/alphaTab/collections/DoubleList.kt index 06466dfbf..7fd70ece2 100644 --- a/packages/kotlin/src/android/src/main/java/alphaTab/collections/DoubleList.kt +++ b/packages/kotlin/src/android/src/main/java/alphaTab/collections/DoubleList.kt @@ -187,4 +187,13 @@ public class DoubleList : IDoubleIterable { for (element in _items) accumulator = operation(accumulator, element) return accumulator } + + public fun some(predicate: (Double) -> Boolean): Boolean { + for (el in this) { + if(predicate(el)) { + return true + } + } + return false + } } diff --git a/packages/lsp/src/alphatex.tmLanguage.json b/packages/lsp/src/alphatex.tmLanguage.json index 90c90ae07..383be75c4 100644 --- a/packages/lsp/src/alphatex.tmLanguage.json +++ b/packages/lsp/src/alphatex.tmLanguage.json @@ -15,7 +15,8 @@ { "include": "#literal" }, { "include": "#expression" }, { "include": "#punctuation-pipe" }, - { "include": "#punctuation-dot" } + { "include": "#punctuation-dot" }, + { "include": "#punctuation-at" } ] }, "string": { @@ -125,14 +126,15 @@ }, "identifier": { "name": "variable.identifier.alphatex", - "match": "[^/\"'-\\.:\\(\\)\\{\\}\\|\\*\\\\\\s][^/\"'\\.:\\(\\)\\{\\}\\|\\*\\\\\\s]+", + "match": "[^@/\"'-\\.:\\(\\)\\{\\}\\|\\*\\\\\\s][^@/\"'\\.:\\(\\)\\{\\}\\|\\*\\\\\\s]+", "captures": { "0": { "name": "variable.identifier.alphatex" } } }, "punctuation": { "patterns": [ { "include": "#punctuation-dot" }, { "include": "#punctuation-pipe" }, - { "include": "#punctuation-asterisk" } + { "include": "#punctuation-asterisk" }, + { "include": "#punctuation-at" } ] }, "punctuation-pipe": { @@ -146,6 +148,10 @@ "punctuation-asterisk": { "name": "punctuation.asterisk.alphatex", "match": "\\*" + }, + "punctuation-at": { + "name": "punctuation.at.alphatex", + "match": "@" } } } \ No newline at end of file