@@ -19,6 +19,7 @@ import { Duration } from '@coderline/alphatab/model/Duration';
1919import { DynamicValue } from '@coderline/alphatab/model/DynamicValue' ;
2020import { FadeType } from '@coderline/alphatab/model/FadeType' ;
2121import { type Fermata , FermataType } from '@coderline/alphatab/model/Fermata' ;
22+ import { FingeringAssigner } from '@coderline/alphatab/model/FingeringAssigner' ;
2223import { Fingers } from '@coderline/alphatab/model/Fingers' ;
2324import { GolpeType } from '@coderline/alphatab/model/GolpeType' ;
2425import { GraceType } from '@coderline/alphatab/model/GraceType' ;
@@ -34,6 +35,7 @@ import type { Note } from '@coderline/alphatab/model/Note';
3435import { NoteAccidentalMode } from '@coderline/alphatab/model/NoteAccidentalMode' ;
3536import { NoteOrnament } from '@coderline/alphatab/model/NoteOrnament' ;
3637import { Ottavia } from '@coderline/alphatab/model/Ottavia' ;
38+ import { PercussionMapper } from '@coderline/alphatab/model/PercussionMapper' ;
3739import { PickStroke } from '@coderline/alphatab/model/PickStroke' ;
3840import { Rasgueado } from '@coderline/alphatab/model/Rasgueado' ;
3941import type { Score } from '@coderline/alphatab/model/Score' ;
@@ -43,6 +45,7 @@ import { SlideOutType } from '@coderline/alphatab/model/SlideOutType';
4345import type { Staff } from '@coderline/alphatab/model/Staff' ;
4446import type { Track } from '@coderline/alphatab/model/Track' ;
4547import { TripletFeel } from '@coderline/alphatab/model/TripletFeel' ;
48+ import { Tuning } from '@coderline/alphatab/model/Tuning' ;
4649import { VibratoType } from '@coderline/alphatab/model/VibratoType' ;
4750import type { Voice } from '@coderline/alphatab/model/Voice' ;
4851import { WahPedal } from '@coderline/alphatab/model/WahPedal' ;
@@ -63,11 +66,13 @@ export class GpifWriter {
6366 private static readonly _sampleRate = 44100 ;
6467
6568 private _rhythmIdLookup : Map < string , string > = new Map < string , string > ( ) ;
69+ private _tuningByStaff : Map < Staff , number [ ] > = new Map < Staff , number [ ] > ( ) ;
6670
6771 public writeXml ( score : Score ) : string {
6872 const xmlDocument = new XmlDocument ( ) ;
6973
7074 this . _rhythmIdLookup = new Map < string , string > ( ) ;
75+ this . _tuningByStaff = new Map < Staff , number [ ] > ( ) ;
7176
7277 this . _writeDom ( xmlDocument , score ) ;
7378
@@ -107,13 +112,38 @@ export class GpifWriter {
107112
108113 for ( const tracks of score . tracks ) {
109114 for ( const staff of tracks . staves ) {
115+ const needsFingering = ModelUtils . staffNotesAreNotStringed ( staff ) ;
116+ const assignersByVoiceIndex = needsFingering ? new Map < number , FingeringAssigner > ( ) : null ;
117+ const stringedTuning = needsFingering ? this . _tuningByStaff . get ( staff ) ! : null ;
118+ // Once the assigner sets note.string/note.fret, note.realValue
119+ // routes through staff.tuning — needs to match the tuning we
120+ // gave the assigner. Save + restore below leaves the input
121+ // model untouched.
122+ const savedTunings = staff . tuning ;
123+ if ( needsFingering && stringedTuning !== null && savedTunings . length === 0 ) {
124+ staff . stringTuning . tunings = stringedTuning . slice ( ) ;
125+ }
126+
110127 for ( const bar of staff . bars ) {
111128 const activeVoices = this . _writeBarNode ( bars , bar ) ;
112129
113130 for ( const voice of activeVoices ) {
131+ let assigner : FingeringAssigner | null = null ;
132+ if ( assignersByVoiceIndex !== null && stringedTuning !== null ) {
133+ if ( assignersByVoiceIndex . has ( voice . index ) ) {
134+ assigner = assignersByVoiceIndex . get ( voice . index ) ! ;
135+ } else {
136+ assigner = new FingeringAssigner ( stringedTuning , staff . capo , staff . transpositionPitch ) ;
137+ assignersByVoiceIndex . set ( voice . index , assigner ) ;
138+ }
139+ }
140+
114141 this . _writeVoiceNode ( voices , voice ) ;
115142
116143 for ( const beat of voice . beats ) {
144+ if ( assigner !== null ) {
145+ assigner . assign ( beat ) ;
146+ }
117147 this . _writeBeatNode ( beats , beat , rhythms ) ;
118148
119149 for ( const note of beat . notes ) {
@@ -122,6 +152,7 @@ export class GpifWriter {
122152 }
123153 }
124154 }
155+ staff . stringTuning . tunings = savedTunings ;
125156 }
126157 }
127158 }
@@ -282,7 +313,13 @@ export class GpifWriter {
282313 this . _writeConcertPitch ( properties , note ) ;
283314 this . _writeTransposedPitch ( properties , note ) ;
284315
285- if ( note . isStringed ) {
316+ if ( note . isPercussion ) {
317+ const art = PercussionMapper . getArticulation ( note ) ;
318+ const midi = art !== null ? art . outputMidiNumber : 0 ;
319+ this . _writeSimplePropertyNode ( properties , 'String' , 'String' , ( note . string - 1 ) . toString ( ) ) ;
320+ this . _writeSimplePropertyNode ( properties , 'Fret' , 'Fret' , midi . toString ( ) ) ;
321+ this . _writeSimplePropertyNode ( properties , 'Midi' , 'Number' , midi . toString ( ) ) ;
322+ } else if ( note . isStringed ) {
286323 this . _writeSimplePropertyNode ( properties , 'String' , 'String' , ( note . string - 1 ) . toString ( ) ) ;
287324 this . _writeSimplePropertyNode ( properties , 'Fret' , 'Fret' , note . fret . toString ( ) ) ;
288325 this . _writeSimplePropertyNode ( properties , 'Midi' , 'Number' , note . realValue . toString ( ) ) ;
@@ -291,10 +328,6 @@ export class GpifWriter {
291328 }
292329 }
293330
294- if ( note . isPercussion ) {
295- this . _writeSimplePropertyNode ( properties , 'String' , 'String' , ( note . string - 1 ) . toString ( ) ) ;
296- }
297-
298331 if ( note . isPiano ) {
299332 this . _writeSimplePropertyNode ( properties , 'Octave' , 'Number' , note . octave . toString ( ) ) ;
300333 this . _writeSimplePropertyNode ( properties , 'Tone' , 'Step' , note . tone . toString ( ) ) ;
@@ -393,7 +426,7 @@ export class GpifWriter {
393426
394427 private _writeTransposedPitch ( properties : XmlNode , note : Note ) {
395428 if ( note . isPercussion ) {
396- this . _writePitch ( properties , 'ConcertPitch ' , 'C' , '-1' , '' ) ;
429+ this . _writePitch ( properties , 'TransposedPitch ' , 'C' , '-1' , '' ) ;
397430 } else {
398431 this . _writePitchForValue (
399432 properties ,
@@ -998,7 +1031,7 @@ export class GpifWriter {
9981031 initialTempoAutomation . addElement ( 'Bar' ) . innerText = '0' ;
9991032 initialTempoAutomation . addElement ( 'Position' ) . innerText = '0' ;
10001033 initialTempoAutomation . addElement ( 'Visible' ) . innerText = 'true' ;
1001- initialTempoAutomation . addElement ( 'Value' ) . innerText = `${ score . tempo } 2` ;
1034+ initialTempoAutomation . addElement ( 'Value' ) . innerText = `${ score . tempo | 0 } 2` ;
10021035 if ( score . tempoLabel ) {
10031036 initialTempoAutomation . addElement ( 'Text' ) . innerText = score . tempoLabel ;
10041037 }
@@ -1024,7 +1057,7 @@ export class GpifWriter {
10241057 tempoAutomation . addElement ( 'Bar' ) . innerText = mb . index . toString ( ) ;
10251058 tempoAutomation . addElement ( 'Position' ) . innerText = automation . ratioPosition . toString ( ) ;
10261059 tempoAutomation . addElement ( 'Visible' ) . innerText = automation . isVisible ? 'true' : 'false' ;
1027- tempoAutomation . addElement ( 'Value' ) . innerText = `${ automation . value } 2` ;
1060+ tempoAutomation . addElement ( 'Value' ) . innerText = `${ automation . value | 0 } 2` ;
10281061 if ( automation . text ) {
10291062 tempoAutomation . addElement ( 'Text' ) . innerText = automation . text ;
10301063 }
@@ -1271,15 +1304,34 @@ export class GpifWriter {
12711304 this . _writeSimplePropertyNode ( properties , 'CapoFret' , 'Fret' , staff . capo . toString ( ) ) ;
12721305 this . _writeSimplePropertyNode ( properties , 'FretCount' , 'Fret' , '24' ) ;
12731306
1274- if ( staff . tuning . length > 0 ) {
1307+ // GP7/8 requires every staff to carry a stringed tuning.
1308+ let tuning = staff . tuning ;
1309+ let tuningName = staff . tuningName ;
1310+ if ( tuning . length === 0 ) {
1311+ if ( staff . isPercussion ) {
1312+ tuning = [ 0 , 0 , 0 , 0 , 0 , 0 ] ;
1313+ tuningName = '' ;
1314+ } else if ( ModelUtils . staffNotesAreNotStringed ( staff ) ) {
1315+ const staffTuning =
1316+ staff . index === 0 ? Tuning . getDefaultTuningFor ( 6 ) : Tuning . getDefaultTuningFor ( 5 ) ;
1317+ tuning = staffTuning ! . tunings ;
1318+ tuningName = staffTuning ! . name ;
1319+ }
1320+ }
1321+ this . _tuningByStaff . set ( staff , tuning ) ;
1322+
1323+ if ( tuning . length > 0 ) {
12751324 const tuningProperty = properties . addElement ( 'Property' ) ;
12761325 tuningProperty . attributes . set ( 'name' , 'Tuning' ) ;
1277- tuningProperty . addElement ( 'Pitches' ) . innerText = staff . tuning . slice ( ) . reverse ( ) . join ( ' ' ) ;
1278- tuningProperty . addElement ( 'Label' ) . setCData ( staff . tuningName ) ;
1279- tuningProperty . addElement ( 'LabelVisible' ) . innerText = staff . tuningName ? 'true' : 'false' ;
1326+ tuningProperty . addElement ( 'Pitches' ) . innerText = tuning . slice ( ) . reverse ( ) . join ( ' ' ) ;
1327+ tuningProperty . addElement ( 'Label' ) . setCData ( tuningName ) ;
1328+ tuningProperty . addElement ( 'LabelVisible' ) . innerText = tuningName ? 'true' : 'false' ;
12801329 tuningProperty . addElement ( 'Flat' ) ;
12811330
1282- switch ( staff . tuning . length ) {
1331+ if ( staff . isPercussion ) {
1332+ tuningProperty . addElement ( 'Instrument' ) . innerText = 'Undefined' ;
1333+ } else {
1334+ switch ( tuning . length ) {
12831335 case 3 :
12841336 tuningProperty . addElement ( 'Instrument' ) . innerText = 'Shamisen' ;
12851337 break ;
@@ -1324,6 +1376,7 @@ export class GpifWriter {
13241376 default :
13251377 tuningProperty . addElement ( 'Instrument' ) . innerText = 'Guitar' ;
13261378 break ;
1379+ }
13271380 }
13281381 }
13291382
0 commit comments