From 79d88c8fcb08346de4ebdd4c2830d0e897c55b92 Mon Sep 17 00:00:00 2001 From: leocaseiro Date: Wed, 4 Mar 2026 22:32:17 +1100 Subject: [PATCH 1/3] feat(drums/tabs): allow showTablature for percussion (drums) notation --- packages/alphatab/src/importer/GpifParser.ts | 10 +- .../src/importer/PartConfiguration.ts | 4 +- packages/alphatab/src/model/Note.ts | 2 +- packages/alphatab/src/model/Staff.ts | 2 - .../src/rendering/TabBarRendererFactory.ts | 1 - .../test-data/guitarpro7/drum-custom-lines.gp | Bin 0 -> 15748 bytes .../test-data/guitarpro7/drum-tabs.gp | Bin 0 -> 14837 bytes .../test/importer/Gp7Importer.test.ts | 46 +++++++ .../test/model/PercussionTablature.test.ts | 129 ++++++++++++++++++ 9 files changed, 179 insertions(+), 15 deletions(-) create mode 100644 packages/alphatab/test-data/guitarpro7/drum-custom-lines.gp create mode 100644 packages/alphatab/test-data/guitarpro7/drum-tabs.gp create mode 100644 packages/alphatab/test/model/PercussionTablature.test.ts diff --git a/packages/alphatab/src/importer/GpifParser.ts b/packages/alphatab/src/importer/GpifParser.ts index ceffcb228..fd629aac2 100644 --- a/packages/alphatab/src/importer/GpifParser.ts +++ b/packages/alphatab/src/importer/GpifParser.ts @@ -979,9 +979,7 @@ export class GpifParser { } } - if (!staff.isPercussion) { - staff.showTablature = true; - } + staff.showTablature = true; break; case 'DiagramCollection': @@ -2779,11 +2777,7 @@ 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; - } else { + if (!staff.isPercussion) { note.percussionArticulation = -1; } beat.addNote(note); 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/model/Note.ts b/packages/alphatab/src/model/Note.ts index 1a55a49a6..6d9f3bf7d 100644 --- a/packages/alphatab/src/model/Note.ts +++ b/packages/alphatab/src/model/Note.ts @@ -238,7 +238,7 @@ export class Note { public tone: number = -1; public get isPercussion(): boolean { - return !this.isStringed && this.percussionArticulation >= 0; + return this.percussionArticulation >= 0; } /** diff --git a/packages/alphatab/src/model/Staff.ts b/packages/alphatab/src/model/Staff.ts index 82cb2e2f9..e6a8eddd5 100644 --- a/packages/alphatab/src/model/Staff.ts +++ b/packages/alphatab/src/model/Staff.ts @@ -123,8 +123,6 @@ export class Staff { public finish(settings: Settings, sharedDataBag: Map | null = null): void { if (this.isPercussion) { - this.stringTuning.tunings = []; - this.showTablature = false; this.displayTranspositionPitch = 0; } this.stringTuning.finish(); diff --git a/packages/alphatab/src/rendering/TabBarRendererFactory.ts b/packages/alphatab/src/rendering/TabBarRendererFactory.ts index df2521c38..bfdcfc2e1 100644 --- a/packages/alphatab/src/rendering/TabBarRendererFactory.ts +++ b/packages/alphatab/src/rendering/TabBarRendererFactory.ts @@ -17,7 +17,6 @@ export class TabBarRendererFactory extends BarRendererFactory { public constructor(effectBands: EffectBandInfo[]) { super(effectBands); - this.hideOnPercussionTrack = true; } public override canCreate(track: Track, staff: Staff): boolean { 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 0000000000000000000000000000000000000000..9f0c2507090bc7834aa54d9c88e13ddc0d89b4f4 GIT binary patch literal 15748 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5ddb629eb288392ab811dcb99d1b3495fd41c5a7 GIT binary patch literal 14837 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 Kfy3qcwEh { expect(score.tracks[0].staves[0].bars[5].voices[0].beats[0].invertBeamDirection).to.be.false; expect(score.tracks[0].staves[0].bars[5].voices[0].beats[0].preferredBeamDirection).to.equal(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).to.be.true; + expect(staff.tuning.length).to.equal(6); + expect(staff.tuning.every((t: number) => t === 0)).to.be.true; + + const beats = staff.bars[0].voices[0].beats; + expect(beats.length).to.equal(4); + + for (const beat of beats) { + for (const note of beat.notes) { + expect(note.isPercussion).to.be.true; + expect(note.isStringed).to.be.true; + expect(note.string).to.be.greaterThanOrEqual(1); + expect(note.string).to.be.lessThanOrEqual(6); + expect(note.fret).to.be.greaterThan(0); + expect(note.percussionArticulation).to.be.greaterThanOrEqual(0); + } + } + }); + + 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).to.be.true; + expect(staff.showTablature).to.be.true; + expect(staff.tuning.length).to.equal(6); + + const beats = staff.bars[0].voices[0].beats; + expect(beats.length).to.equal(4); + + expect(beats[0].notes[0].string).to.equal(5); + expect(beats[0].notes[0].fret).to.equal(36); + expect(beats[1].notes[0].string).to.equal(4); + expect(beats[1].notes[0].fret).to.equal(36); + expect(beats[2].notes[0].string).to.equal(3); + expect(beats[2].notes[0].fret).to.equal(36); + expect(beats[3].notes[0].string).to.equal(2); + expect(beats[3].notes[0].fret).to.equal(36); + }); }); diff --git a/packages/alphatab/test/model/PercussionTablature.test.ts b/packages/alphatab/test/model/PercussionTablature.test.ts new file mode 100644 index 000000000..6fdf96f18 --- /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 { expect } from 'chai'; + +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).to.be.true; + expect(note.isStringed).to.be.true; + }); + + it('returns true when percussionArticulation is set without string', () => { + const note = new Note(); + note.percussionArticulation = 38; + + expect(note.isPercussion).to.be.true; + expect(note.isStringed).to.be.false; + }); + + it('returns false when percussionArticulation is not set', () => { + const note = new Note(); + note.string = 1; + note.fret = 5; + + expect(note.isPercussion).to.be.false; + expect(note.isStringed).to.be.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).to.be.true; + expect(staff.tuning.length).to.equal(6); + expect(staff.displayTranspositionPitch).to.equal(0); + }); + + it('disables 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).to.be.false; + expect(staff.tuning.length).to.equal(0); + }); + + 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).to.equal(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).to.be.true; + expect(staff.tuning.length).to.equal(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)).to.be.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)).to.be.false; + }); + + it('rejects percussion staff without tuning', () => { + const factory = new TabBarRendererFactory([]); + const [track, staff] = createStaff(true, true, []); + + expect(factory.canCreate(track, staff)).to.be.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)).to.be.true; + }); + }); +}); From c49ce2a41aafea08971746b0700831aaec8f4579 Mon Sep 17 00:00:00 2001 From: leocaseiro Date: Wed, 4 Mar 2026 22:54:45 +1100 Subject: [PATCH 2/3] add drum-tabs.gp file --- .../test-data/guitarpro7/drum-tabs.gp | Bin 14837 -> 17619 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/packages/alphatab/test-data/guitarpro7/drum-tabs.gp b/packages/alphatab/test-data/guitarpro7/drum-tabs.gp index 5ddb629eb288392ab811dcb99d1b3495fd41c5a7..b055181dd4f4a06498ca1bdcf905f130cb8ccfb1 100644 GIT binary patch literal 17619 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 U`dJGA=Hu`6qk+zkivR%s2Sjx+i~s-t literal 14837 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 Kfy3qcwEh Date: Thu, 5 Mar 2026 12:54:11 +1100 Subject: [PATCH 3/3] fix(drums/tab): make sure all tabs are notated forcing string 5 for 5+ strings --- packages/alphatab/src/importer/GpifParser.ts | 3 + .../alphatab/test/importer/GpifParser.test.ts | 122 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 packages/alphatab/test/importer/GpifParser.test.ts diff --git a/packages/alphatab/src/importer/GpifParser.ts b/packages/alphatab/src/importer/GpifParser.ts index fd629aac2..9a43f1623 100644 --- a/packages/alphatab/src/importer/GpifParser.ts +++ b/packages/alphatab/src/importer/GpifParser.ts @@ -2779,6 +2779,9 @@ export class GpifParser { const note = NoteCloner.clone(this._noteById.get(noteId)!); if (!staff.isPercussion) { note.percussionArticulation = -1; + } else if (note.string > 5) { + // Drum notation uses 5 lines; string 6+ won't render + note.string = 5; } beat.addNote(note); if (this._tappedNotes.has(noteId)) { diff --git a/packages/alphatab/test/importer/GpifParser.test.ts b/packages/alphatab/test/importer/GpifParser.test.ts new file mode 100644 index 000000000..5814d7242 --- /dev/null +++ b/packages/alphatab/test/importer/GpifParser.test.ts @@ -0,0 +1,122 @@ +import { GpifParser } from '@coderline/alphatab/importer/GpifParser'; +import { Settings } from '@coderline/alphatab/Settings'; +import { expect } from 'chai'; + +describe('GpifParser', () => { + describe('drum string clamping', () => { + it('clamps drum note string 6 to 5', () => { + // Minimal GPIF with drum track and note with String 6 (0-based: 5 -> our string 6) + const gpif = ` + +1 + +0 + + +Drums +Dr +0 0 0 + +0099 + +0 0 0 0 0 0 + + + + +00Major + + +0 + + +0 + + +0 + + +Quarter + + + +36 + +5 +36 + + + +`; + + const parser = new GpifParser(); + parser.parseXml(gpif, new Settings()); + + const staff = parser.score.tracks[0].staves[0]; + expect(staff.isPercussion).to.be.true; + + const note = parser.score.tracks[0].staves[0].bars[0].voices[0].beats[0].notes[0]; + // GPIF String 5 = 0-based index 5 -> our string 6. Should be clamped to 5. + expect(note.string).to.equal(5); + expect(note.percussionArticulation).to.equal(36); + }); + + it('preserves drum note string 1-5', () => { + const gpif = ` + +1 + +0 + + +Drums +Dr +0 0 0 + +0099 + +0 0 0 0 0 0 + + + + +00Major + + +0 + + +0 1 2 3 4 + + +0 +1 +2 +3 +4 + + +Quarter + + +36036 +36136 +36236 +36336 +36436 + +`; + + const parser = new GpifParser(); + parser.parseXml(gpif, new Settings()); + + const beats = parser.score.tracks[0].staves[0].bars[0].voices[0].beats; + // GPIF String 0->4 = our strings 1->5 + expect(beats[0].notes[0].string).to.equal(1); + expect(beats[1].notes[0].string).to.equal(2); + expect(beats[2].notes[0].string).to.equal(3); + expect(beats[3].notes[0].string).to.equal(4); + expect(beats[4].notes[0].string).to.equal(5); + }); + }); +});