From 73548120691d908fd6f5c6e28fd85309d792d7e5 Mon Sep 17 00:00:00 2001 From: danielku15 Date: Sun, 1 Feb 2026 20:08:21 +0100 Subject: [PATCH] fix: handle GP5 percussion instruments which were removed in later GP versions --- .../alphatab/src/importer/Gp3To5Importer.ts | 24 +++++++++++++++++- .../alphatab/src/model/PercussionMapper.ts | 3 ++- .../test-data/guitarpro5/percussion-all.gp5 | Bin 0 -> 2287 bytes .../test/exporter/AlphaTexExporter.test.ts | 4 +++ .../test/importer/Gp5Importer.test.ts | 16 ++++++++++++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 packages/alphatab/test-data/guitarpro5/percussion-all.gp5 diff --git a/packages/alphatab/src/importer/Gp3To5Importer.ts b/packages/alphatab/src/importer/Gp3To5Importer.ts index 16223924d..cc74873be 100644 --- a/packages/alphatab/src/importer/Gp3To5Importer.ts +++ b/packages/alphatab/src/importer/Gp3To5Importer.ts @@ -54,6 +54,26 @@ import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection */ export class Gp3To5Importer extends ScoreImporter { private static readonly _versionString: string = 'FICHIER GUITAR PRO '; + + // NOTE: General Midi only defines percussion instruments from 35-81 + // Guitar Pro 5 allowed GS extensions (27-34 and 82-87) + // GP7-8 do not have all these definitions anymore, this lookup ensures some fallback + // (even if they are not correct) + // we can support this properly in future when we allow custom alphaTex articulation definitions + // then we don't need to rely on GP specifics anymore but handle things on export/import + private static readonly _gp5PercussionInstrumentMap = new Map([ + // High Q -> GS "High Q / Filter Snap" + [27, 42], + // Slap + [28, 60], + // Scratch Push + [29, 29], + // Scratch Pull + [30, 30], + // Square Click + [32, 31] + ]); + private _versionNumber: number = 0; private _score!: Score; private _globalTripletFeel: TripletFeel = TripletFeel.NoTripletFeel; @@ -1225,7 +1245,9 @@ export class Gp3To5Importer extends ScoreImporter { } if (bar.staff.isPercussion) { - newNote.percussionArticulation = newNote.fret; + newNote.percussionArticulation = Gp3To5Importer._gp5PercussionInstrumentMap.has(newNote.fret) + ? Gp3To5Importer._gp5PercussionInstrumentMap.get(newNote.fret)! + : newNote.fret; newNote.string = -1; newNote.fret = -1; } diff --git a/packages/alphatab/src/model/PercussionMapper.ts b/packages/alphatab/src/model/PercussionMapper.ts index cbc6e0942..6932bbcd2 100644 --- a/packages/alphatab/src/model/PercussionMapper.ts +++ b/packages/alphatab/src/model/PercussionMapper.ts @@ -1068,7 +1068,8 @@ export class PercussionMapper { } } - return 'unknown'; + // unknown combination, should not happen, fallback to some default value (Snare hit) + return 'Snare (hit)'; } public static getArticulation(n: Note): InstrumentArticulation | null { diff --git a/packages/alphatab/test-data/guitarpro5/percussion-all.gp5 b/packages/alphatab/test-data/guitarpro5/percussion-all.gp5 new file mode 100644 index 0000000000000000000000000000000000000000..6b877c301a5d156f3c0d230301d6ea38ed0df04f GIT binary patch literal 2287 zcmeH{Yi|-k6owayEv0Q*V>BAAj+d#T_NK-A1z0GoLRohgi60ThN`hc^p|Rimjs77# zXSZ)j6Q&{2`pJ{*&NFk~cW2M+>|EZp%(7KJ;*-FiOi&QmujbB<@Zo{V(4 zO1@r|=P9w$==<$Jd_EMiQF97zG*}%5omMzlt-F>fuaj~$Qm%<{GIT_WOQg7H_P-zc zo&B!^Ahckh2RG|C=)fVf*>J60NYYsa7Ub8F4ZdbL2zG^R# zbJ1?@D!Z8!@Q5TEP zB+kvwCFfJ4nV&~VE{WHGyIkUO!5@AL=zuAxFU?_hut;MI(U{loslcG>DY7DV6>tr( z3g9!3ADj&b#+DmlpS%Ka6L5=QlI1o8cK{Nwrm@_G;2t0ixUaE1fZ!ou9k8LXY(lUF z*ake(SRO;L1IPgQq@Ps&QwW{`o&#QJEH5E=1$Yg3qp`e&APdL=@&uD!%!Hr-C<1mh wmJ$SIfCYG`vAl<%0;mG^G?p3!HsAxm(O6svJU|`r@uY=UBHaA^pN*I0H$p16u>b%7 literal 0 HcmV?d00001 diff --git a/packages/alphatab/test/exporter/AlphaTexExporter.test.ts b/packages/alphatab/test/exporter/AlphaTexExporter.test.ts index cd6001146..4faf7e7a2 100644 --- a/packages/alphatab/test/exporter/AlphaTexExporter.test.ts +++ b/packages/alphatab/test/exporter/AlphaTexExporter.test.ts @@ -168,6 +168,10 @@ describe('AlphaTexExporterTest', () => { await testRoundTripEqual(`conversion/full-song.gp5`); }); + it('gp5-articulation', async () => { + await testRoundTripEqual(`guitarpro5/percussion-all.gp5`); + }); + it('gp6-to-alphaTex', async () => { await testRoundTripEqual(`conversion/full-song.gpx`); }); diff --git a/packages/alphatab/test/importer/Gp5Importer.test.ts b/packages/alphatab/test/importer/Gp5Importer.test.ts index 839203631..4ef957e89 100644 --- a/packages/alphatab/test/importer/Gp5Importer.test.ts +++ b/packages/alphatab/test/importer/Gp5Importer.test.ts @@ -9,6 +9,7 @@ import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection import { GpImporterTestHelper } from 'test/importer/GpImporterTestHelper'; import { expect } from 'chai'; import { Clef } from '@coderline/alphatab/model/Clef'; +import { PercussionMapper } from '@coderline/alphatab/model/PercussionMapper'; describe('Gp5ImporterTest', () => { it('score-info', async () => { @@ -553,4 +554,19 @@ describe('Gp5ImporterTest', () => { expect(score.tracks[2].staves[0].bars[0].clef).to.equal(Clef.F4); expect(score.tracks[3].staves[0].bars[0].clef).to.equal(Clef.F4); }); + + it('percusson', async () => { + const score = (await GpImporterTestHelper.prepareImporterWithFile('guitarpro5/percussion-all.gp5')).readScore(); + + let beat: Beat | null = score.tracks[0].staves[0].bars[0].voices[0].beats[0]; + + while (beat) { + if (beat.notes.length === 1) { + const articulationName = PercussionMapper.getArticulationName(beat.notes[0]); + const hasArticulation = PercussionMapper.instrumentArticulationNames.has(articulationName); + expect(hasArticulation).to.be.true; + beat = beat.nextBeat; + } + } + }); });