Skip to content

Commit f22255c

Browse files
committed
fix(gpexporter): respect key signature on accidental writing
1 parent 9427245 commit f22255c

7 files changed

Lines changed: 299 additions & 219 deletions

File tree

src/exporter/GpifWriter.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { GeneralMidi } from '@src/midi/GeneralMidi';
33
import { MidiFileGenerator } from '@src/midi/MidiFileGenerator';
44
import { MidiUtils } from '@src/midi/MidiUtils';
55
import { AccentuationType } from '@src/model/AccentuationType';
6+
import { AccidentalType } from '@src/model/AccidentalType';
67
import { type Automation, AutomationType } from '@src/model/Automation';
78
import { type Bar, SustainPedalMarkerType } from '@src/model/Bar';
89
import { BarreShape } from '@src/model/BarreShape';
@@ -21,9 +22,11 @@ import { GolpeType } from '@src/model/GolpeType';
2122
import { GraceType } from '@src/model/GraceType';
2223
import { HarmonicType } from '@src/model/HarmonicType';
2324
import { TechniqueSymbolPlacement } from '@src/model/InstrumentArticulation';
25+
import type { KeySignature } from '@src/model/KeySignature';
2426
import { KeySignatureType } from '@src/model/KeySignatureType';
2527
import { Lyrics } from '@src/model/Lyrics';
2628
import type { MasterBar } from '@src/model/MasterBar';
29+
import { ModelUtils } from '@src/model/ModelUtils';
2730
import { MusicFontSymbol } from '@src/model/MusicFontSymbol';
2831
import type { Note } from '@src/model/Note';
2932
import { NoteAccidentalMode } from '@src/model/NoteAccidentalMode';
@@ -40,7 +43,6 @@ import { SlideOutType } from '@src/model/SlideOutType';
4043
import type { Staff } from '@src/model/Staff';
4144
import type { Track } from '@src/model/Track';
4245
import { TripletFeel } from '@src/model/TripletFeel';
43-
import { Tuning } from '@src/model/Tuning';
4446
import { VibratoType } from '@src/model/VibratoType';
4547
import type { Voice } from '@src/model/Voice';
4648
import { WahPedal } from '@src/model/WahPedal';
@@ -571,23 +573,26 @@ export class GpifWriter {
571573
if (note.isPercussion) {
572574
this.writePitch(properties, 'ConcertPitch', 'C', '-1', '');
573575
} else {
574-
this.writePitchForValue(properties, 'TransposedPitch', note.displayValueWithoutBend, note.accidentalMode);
576+
this.writePitchForValue(properties, 'TransposedPitch', note.displayValueWithoutBend, note.accidentalMode, note.beat.voice.bar.keySignature);
575577
}
576578
}
577579

578580
private writeConcertPitch(properties: XmlNode, note: Note) {
579581
if (note.isPercussion) {
580582
this.writePitch(properties, 'ConcertPitch', 'C', '-1', '');
581583
} else {
582-
this.writePitchForValue(properties, 'ConcertPitch', note.realValueWithoutHarmonic, note.accidentalMode);
584+
this.writePitchForValue(properties, 'ConcertPitch', note.realValueWithoutHarmonic, note.accidentalMode, note.beat.voice.bar.keySignature);
583585
}
584586
}
585587

588+
private static readonly defaultSteps: string[] = ['C', 'C', 'D', 'D', 'E', 'F', 'F', 'G', 'G', 'A', 'A', 'B'];
589+
586590
private writePitchForValue(
587591
properties: XmlNode,
588592
propertyName: string,
589593
value: number,
590-
accidentalMode: NoteAccidentalMode
594+
accidentalMode: NoteAccidentalMode,
595+
keySignature: KeySignature
591596
) {
592597
let index = 0;
593598
let octave = 0;
@@ -599,8 +604,25 @@ export class GpifWriter {
599604
index = value % 12;
600605
octave = (value / 12) | 0;
601606

602-
step = Tuning.defaultSteps[index];
603-
accidental = Tuning.defaultAccidentals[index];
607+
step = GpifWriter.defaultSteps[index];
608+
switch (ModelUtils.computeAccidental(keySignature, NoteAccidentalMode.Default, value, false)) {
609+
case AccidentalType.None:
610+
case AccidentalType.Natural:
611+
accidental = '';
612+
break;
613+
case AccidentalType.Sharp:
614+
accidental = '#';
615+
break;
616+
case AccidentalType.Flat:
617+
accidental = 'b';
618+
break;
619+
case AccidentalType.DoubleSharp:
620+
accidental = 'x';
621+
break;
622+
case AccidentalType.DoubleFlat:
623+
accidental = 'bb';
624+
break;
625+
}
604626
};
605627
updateParts();
606628

@@ -1183,10 +1205,13 @@ export class GpifWriter {
11831205

11841206
value.addElement('BarIndex').innerText = mb.index.toString();
11851207
value.addElement('BarOccurrence').innerText = syncPoint.syncPointValue!.barOccurence.toString();
1186-
value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value.get(syncPoint)!.syncBpm.toString();
1208+
value.addElement('ModifiedTempo').innerText = modifiedTempoLookup.value
1209+
.get(syncPoint)!
1210+
.syncBpm.toString();
11871211
value.addElement('OriginalTempo').innerText = score.tempo.toString();
1188-
let frameOffset = (((syncPoint.syncPointValue!.millisecondOffset - millisecondPadding) / 1000) *
1189-
GpifWriter.SampleRate);
1212+
let frameOffset =
1213+
((syncPoint.syncPointValue!.millisecondOffset - millisecondPadding) / 1000) *
1214+
GpifWriter.SampleRate;
11901215
frameOffset = Math.floor(frameOffset + 0.5);
11911216
value.addElement('FrameOffset').innerText = frameOffset.toString();
11921217
}
@@ -1787,8 +1812,19 @@ export class GpifWriter {
17871812
const masterBarNode = parent.addElement('MasterBar');
17881813

17891814
const key = masterBarNode.addElement('Key');
1790-
key.addElement('AccidentalCount').innerText = (masterBar.keySignature as number).toString();
1791-
key.addElement('Mode').innerText = KeySignatureType[masterBar.keySignatureType];
1815+
1816+
let keySignature = masterBar.score.tracks[0].staves[0].bars[masterBar.index].keySignature;
1817+
const keySignatureType = masterBar.score.tracks[0].staves[0].bars[masterBar.index].keySignatureType;
1818+
1819+
// reverse transpose
1820+
const transposeIndex = ModelUtils.flooredDivision(
1821+
masterBar.score.tracks[0].staves[0].displayTranspositionPitch,
1822+
12
1823+
);
1824+
keySignature = ModelUtils.transposeKey(keySignature, -transposeIndex);
1825+
1826+
key.addElement('AccidentalCount').innerText = (keySignature as number).toString();
1827+
key.addElement('Mode').innerText = KeySignatureType[keySignatureType];
17921828
key.addElement('Sharps').innerText = 'Sharps';
17931829

17941830
masterBarNode.addElement('Time').innerText =

src/importer/Gp3To5Importer.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ import { Voice } from '@src/model/Voice';
4040
import { Logger } from '@src/Logger';
4141
import { ModelUtils } from '@src/model/ModelUtils';
4242
import type { IWriteable } from '@src/io/IWriteable';
43-
import { Tuning } from '@src/model/Tuning';
4443
import { FadeType } from '@src/model/FadeType';
4544
import { Rasgueado } from '@src/model/Rasgueado';
4645
import { Direction } from '@src/model/Direction';
4746
import { BeamDirection } from '@src/rendering/utils/BeamDirection';
4847
import { Ottavia } from '@src/model/Ottavia';
4948
import { WahPedal } from '@src/model/WahPedal';
49+
import { AccidentalType } from '@src/model/AccidentalType';
5050

5151
export class Gp3To5Importer extends ScoreImporter {
5252
private static readonly VersionString: string = 'FICHIER GUITAR PRO ';
@@ -1128,10 +1128,16 @@ export class Gp3To5Importer extends ScoreImporter {
11281128
newNote.fret = -1;
11291129
}
11301130
if (swapAccidentals) {
1131-
const accidental = Tuning.defaultAccidentals[newNote.realValueWithoutHarmonic % 12];
1132-
if (accidental === '#') {
1131+
const accidental = ModelUtils.computeAccidental(
1132+
bar.keySignature,
1133+
NoteAccidentalMode.Default,
1134+
newNote.realValueWithoutHarmonic,
1135+
false
1136+
);
1137+
1138+
if (accidental === AccidentalType.Sharp) {
11331139
newNote.accidentalMode = NoteAccidentalMode.ForceFlat;
1134-
} else if (accidental === 'b') {
1140+
} else if (accidental === AccidentalType.Flat) {
11351141
newNote.accidentalMode = NoteAccidentalMode.ForceSharp;
11361142
}
11371143
// Note: forcing no sign to sharp not supported

src/importer/GpifParser.ts

Lines changed: 11 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ import { Rasgueado } from '@src/model/Rasgueado';
5454
import { Direction } from '@src/model/Direction';
5555
import { ModelUtils } from '@src/model/ModelUtils';
5656
import { BackingTrack } from '@src/model/BackingTrack';
57+
import { Tuning } from '@src/model/Tuning';
5758

5859
/**
5960
* This structure represents a duration within a gpif
@@ -638,7 +639,7 @@ export class GpifParser {
638639
track.playbackInfo.isMute = state === 'Mute';
639640
break;
640641
case 'PartSounding':
641-
this.parsePartSounding(track, c);
642+
this.parsePartSounding(trackId, track, c);
642643
break;
643644
case 'Staves':
644645
this.parseStaves(track, c);
@@ -1207,14 +1208,18 @@ export class GpifParser {
12071208
}
12081209
}
12091210

1210-
private parsePartSounding(track: Track, node: XmlNode): void {
1211+
private parsePartSounding(trackId: string, track: Track, node: XmlNode): void {
12111212
for (const c of node.childElements()) {
12121213
switch (c.localName) {
12131214
case 'TranspositionPitch':
12141215
for (const staff of track.staves) {
12151216
staff.displayTranspositionPitch = GpifParser.parseIntSafe(c.innerText, 0);
12161217
}
12171218
break;
1219+
case 'NominalKey':
1220+
const transposeIndex = Math.max(0, Tuning.noteNames.indexOf(c.innerText));
1221+
this._transposeKeySignaturePerTrack.set(trackId, transposeIndex);
1222+
break;
12181223
}
12191224
}
12201225
}
@@ -1242,14 +1247,10 @@ export class GpifParser {
12421247

12431248
// the chromatic transpose also causes an alternative key signature to be adjusted
12441249
// In Guitar Pro this feature is hidden in the track properties (more -> Transposition tonality -> 'C played as:' ).
1245-
const transposeIndex = GpifParser.flooredDivision(pitch, 12);
1250+
const transposeIndex = ModelUtils.flooredDivision(pitch, 12);
12461251
this._transposeKeySignaturePerTrack.set(trackId, transposeIndex);
12471252
}
12481253

1249-
private static flooredDivision(a: number, b: number): number {
1250-
return a - b * Math.floor(a / b);
1251-
}
1252-
12531254
private parseRSE(track: Track, node: XmlNode): void {
12541255
for (const c of node.childElements()) {
12551256
switch (c.localName) {
@@ -2612,7 +2613,7 @@ export class GpifParser {
26122613
keySignature = [KeySignature.C, KeySignatureType.Major];
26132614
if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[0])) {
26142615
keySignature = [
2615-
GpifParser.transposeKey(
2616+
ModelUtils.transposeKey(
26162617
keySignature[0],
26172618
this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[0])!
26182619
),
@@ -2637,7 +2638,7 @@ export class GpifParser {
26372638
keySignature = this._keySignatures.get(masterBarIndex)!;
26382639
if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[trackIndex])) {
26392640
keySignature = [
2640-
GpifParser.transposeKey(
2641+
ModelUtils.transposeKey(
26412642
keySignature[0],
26422643
this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[trackIndex])!
26432644
),
@@ -2718,7 +2719,7 @@ export class GpifParser {
27182719
keySignature = [KeySignature.C, KeySignatureType.Major];
27192720
if (this._transposeKeySignaturePerTrack.has(trackIndexToTrackId[trackIndex])) {
27202721
keySignature = [
2721-
GpifParser.transposeKey(
2722+
ModelUtils.transposeKey(
27222723
keySignature[0],
27232724
this._transposeKeySignaturePerTrack.get(trackIndexToTrackId[trackIndex])!
27242725
),
@@ -2804,55 +2805,4 @@ export class GpifParser {
28042805
}
28052806
}
28062807
}
2807-
2808-
private static transposeKey(keySignature: KeySignature, transpose: number): KeySignature {
2809-
return GpifParser.keyTransposeTable[transpose][keySignature + 7];
2810-
}
2811-
2812-
// NOTE: haven't figured out yet what exact formula is applied when transposing key signatures
2813-
// this table is simply created by checking the Guitar Pro behavior,
2814-
2815-
// The table is organized as [<transpose>][<key signature>] to match the table above
2816-
// it's also easier to read as we list every key signature per row, transposed by the same value
2817-
// this gives typically just a shifted list according to the transpose (with some special treatments)
2818-
2819-
private static readonly keyTransposeTable: KeySignature[][] = GpifParser.translateKeyTransposeTable([
2820-
/* Cb Gb Db Ab Eb Bb F C G D A E B F C# */
2821-
/* C 0 */ ['7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#'],
2822-
/* Db 1 */ ['2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b', '1b', '0#'],
2823-
/* D 2 */ ['3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#'],
2824-
/* Eb 3 */ ['4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b'],
2825-
/* E 4 */ ['1#', '2#', '3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#'],
2826-
/* F 5 */ ['6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b'],
2827-
/* Gb 6 */ ['1b', '0#', '1#', '2#', '3#', '4#', '7b', '6#', '7#', '4b', '3b', '2b', '1b', '0#', '1#'],
2828-
/* G 7 */ ['4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#'],
2829-
/* Ab 8 */ ['3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b', '2b', '1b'],
2830-
/* A 9 */ ['2#', '3#', '4#', '7b', '6b', '5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#'],
2831-
/* Bb 10 */ ['5b', '4b', '3b', '2b', '1b', '0#', '1#', '2#', '3#', '4#', '5#', '6#', '7#', '4b', '3b'],
2832-
/* B 11 */ ['0#', '1#', '2#', '3#', '4#', '7b', '6b', '6#', '4b', '3b', '2b', '1b', '0#', '1#', '2#']
2833-
]);
2834-
2835-
/**
2836-
* Converts the key transpose table to actual key signatures.
2837-
* @param texts An array where every item indicates the number of accidentals and which accidental
2838-
* placed for the key signature.
2839-
*
2840-
* e.g. 3# is 3-sharps -> KeySignature.A
2841-
*/
2842-
static translateKeyTransposeTable(texts: string[][]): KeySignature[][] {
2843-
const keySignatures: KeySignature[][] = [];
2844-
for (const transpose of texts) {
2845-
const transposeValues: KeySignature[] = [];
2846-
keySignatures.push(transposeValues);
2847-
for (const keySignatureText of transpose) {
2848-
transposeValues.push(
2849-
// digit
2850-
parseInt(keySignatureText.charAt(0)) *
2851-
// b -> negative, # positive
2852-
(keySignatureText.charAt(1) === 'b' ? -1 : 1)
2853-
);
2854-
}
2855-
}
2856-
return keySignatures;
2857-
}
28582808
}

0 commit comments

Comments
 (0)