Skip to content

Commit 870f57e

Browse files
authored
refactor: Score Tempo handling (#2340)
1 parent 74a7b15 commit 870f57e

21 files changed

+281
-60
lines changed

packages/alphatab/src/exporter/AlphaTexExporter.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -670,11 +670,15 @@ export class AlphaTexExporter extends ScoreExporter {
670670
}
671671

672672
for (const a of masterBar.tempoAutomations) {
673-
writer.write(`\\tempo { ${a.value} `);
673+
writer.write(`\\tempo ( ${a.value} `);
674674
if (a.text) {
675675
writer.writeString(a.text);
676676
}
677-
writer.writeLine(`${a.ratioPosition} }`);
677+
writer.write(`${a.ratioPosition} `);
678+
if (!a.isVisible) {
679+
writer.writeLine('hide ');
680+
}
681+
writer.writeLine(`)`);
678682
}
679683

680684
writer.dropSingleLineComment();

packages/alphatab/src/exporter/GpifWriter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1198,7 +1198,7 @@ export class GpifWriter {
11981198
tempoAutomation.addElement('Linear').innerText = automation.isLinear ? 'true' : 'false';
11991199
tempoAutomation.addElement('Bar').innerText = mb.index.toString();
12001200
tempoAutomation.addElement('Position').innerText = automation.ratioPosition.toString();
1201-
tempoAutomation.addElement('Visible').innerText = 'true';
1201+
tempoAutomation.addElement('Visible').innerText = automation.isVisible ? 'true' : 'false';
12021202
tempoAutomation.addElement('Value').innerText = `${automation.value} 2`;
12031203
if (automation.text) {
12041204
tempoAutomation.addElement('Text').innerText = automation.text;

packages/alphatab/src/generated/model/AutomationCloner.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export class AutomationCloner {
1414
clone.syncPointValue = original.syncPointValue ? SyncPointDataCloner.clone(original.syncPointValue) : undefined;
1515
clone.ratioPosition = original.ratioPosition;
1616
clone.text = original.text;
17+
clone.isVisible = original.isVisible;
1718
return clone;
1819
}
1920
}

packages/alphatab/src/generated/model/AutomationSerializer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export class AutomationSerializer {
2828
}
2929
o.set("ratioposition", obj.ratioPosition);
3030
o.set("text", obj.text);
31+
o.set("isvisible", obj.isVisible);
3132
return o;
3233
}
3334
public static setProperty(obj: Automation, property: string, v: unknown): boolean {
@@ -56,6 +57,9 @@ export class AutomationSerializer {
5657
case "text":
5758
obj.text = v! as string;
5859
return true;
60+
case "isvisible":
61+
obj.isVisible = v! as boolean;
62+
return true;
5963
}
6064
return false;
6165
}

packages/alphatab/src/generated/model/ScoreSerializer.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ export class ScoreSerializer {
3636
o.set("title", obj.title);
3737
o.set("words", obj.words);
3838
o.set("tab", obj.tab);
39-
o.set("tempo", obj.tempo);
40-
o.set("tempolabel", obj.tempoLabel);
4139
o.set("masterbars", obj.masterBars.map(i => MasterBarSerializer.toJson(i)));
4240
o.set("tracks", obj.tracks.map(i => TrackSerializer.toJson(i)));
4341
o.set("defaultsystemslayout", obj.defaultSystemsLayout);
@@ -83,12 +81,6 @@ export class ScoreSerializer {
8381
case "tab":
8482
obj.tab = v! as string;
8583
return true;
86-
case "tempo":
87-
obj.tempo = v! as number;
88-
return true;
89-
case "tempolabel":
90-
obj.tempoLabel = v! as string;
91-
return true;
9284
case "masterbars":
9385
obj.masterBars = [];
9486
for (const o of (v as (Map<string, unknown> | null)[])) {

packages/alphatab/src/importer/AlphaTexImporter.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,7 @@ export class AlphaTexImporter extends ScoreImporter {
556556
private _currentStaff!: Staff;
557557
private _barIndex: number = 0;
558558
private _voiceIndex: number = 0;
559+
private _initialTempo = Automation.buildTempoAutomation(false, 0, 120, 0);
559560

560561
// Last known position that had valid syntax/symbols
561562
private _currentDuration: Duration = Duration.QuadrupleWhole;
@@ -768,8 +769,6 @@ export class AlphaTexImporter extends ScoreImporter {
768769
*/
769770
private createDefaultScore(): void {
770771
this._score = new Score();
771-
this._score.tempo = 120;
772-
this._score.tempoLabel = '';
773772
this.newTrack();
774773
}
775774

@@ -1073,13 +1072,13 @@ export class AlphaTexImporter extends ScoreImporter {
10731072
case 'tempo':
10741073
this.sy = this.newSy(true);
10751074
if (this.sy === AlphaTexSymbols.Number) {
1076-
this._score.tempo = this.syData as number;
1075+
this._initialTempo.value = this.syData as number;
10771076
} else {
10781077
this.error('tempo', AlphaTexSymbols.Number, true);
10791078
}
10801079
this.sy = this.newSy();
10811080
if (this.sy === AlphaTexSymbols.String) {
1082-
this._score.tempoLabel = this.syData as string;
1081+
this._initialTempo.text = this.syData as string;
10831082
this.sy = this.newSy();
10841083
}
10851084
anyTopLevelMeta = true;
@@ -1878,6 +1877,8 @@ export class AlphaTexImporter extends ScoreImporter {
18781877
master.timeSignatureDenominator = master.previousMasterBar!.timeSignatureDenominator;
18791878
master.timeSignatureNumerator = master.previousMasterBar!.timeSignatureNumerator;
18801879
master.tripletFeel = master.previousMasterBar!.tripletFeel;
1880+
} else {
1881+
master.tempoAutomations.push(this._initialTempo);
18811882
}
18821883
}
18831884
const anyBarMeta = this.barMeta(bar);
@@ -2367,8 +2368,19 @@ export class AlphaTexImporter extends ScoreImporter {
23672368
} else if (syData === 'tempo') {
23682369
// NOTE: playbackRatio is calculated on score finish when playback positions are known
23692370
const tempoAutomation = this.readTempoAutomation(false);
2371+
2372+
if (beat.index === 0) {
2373+
const existing = beat.voice.bar.masterBar.tempoAutomations.find(a => a.ratioPosition === 0);
2374+
if (existing) {
2375+
existing.value = tempoAutomation.value;
2376+
existing.text = tempoAutomation.text;
2377+
beat.automations.push(existing);
2378+
return true;
2379+
}
2380+
}
23702381
beat.automations.push(tempoAutomation);
23712382
beat.voice.bar.masterBar.tempoAutomations.push(tempoAutomation);
2383+
23722384
return true;
23732385
} else if (syData === 'volume') {
23742386
// NOTE: playbackRatio is calculated on score finish when playback positions are known
@@ -3326,7 +3338,14 @@ export class AlphaTexImporter extends ScoreImporter {
33263338
} else if (syData === 'tempo') {
33273339
const tempoAutomation = this.readTempoAutomation(true);
33283340

3329-
master.tempoAutomations.push(tempoAutomation);
3341+
const existing = master.tempoAutomations.find(a => a.ratioPosition === tempoAutomation.ratioPosition);
3342+
if (existing) {
3343+
existing.value = tempoAutomation.value;
3344+
existing.text = tempoAutomation.text;
3345+
existing.isVisible = tempoAutomation.isVisible;
3346+
} else {
3347+
master.tempoAutomations.push(tempoAutomation);
3348+
}
33303349
} else if (syData === 'section') {
33313350
this.sy = this.newSy();
33323351
if (this.sy !== AlphaTexSymbols.String) {
@@ -3624,7 +3643,7 @@ export class AlphaTexImporter extends ScoreImporter {
36243643
tempoAutomation.isLinear = false;
36253644
tempoAutomation.type = AutomationType.Tempo;
36263645

3627-
if (this.sy === AlphaTexSymbols.LBrace && withPosition) {
3646+
if (this.sy === AlphaTexSymbols.LParensis && withPosition) {
36283647
this.sy = this.newSy(true);
36293648
if (this.sy !== AlphaTexSymbols.Number) {
36303649
this.error('tempo', AlphaTexSymbols.Number, true);
@@ -3644,8 +3663,13 @@ export class AlphaTexImporter extends ScoreImporter {
36443663
tempoAutomation.ratioPosition = this.syData as number;
36453664
this.sy = this.newSy();
36463665

3647-
if (this.sy !== AlphaTexSymbols.RBrace) {
3648-
this.error('tempo', AlphaTexSymbols.RBrace, true);
3666+
if (this.sy === AlphaTexSymbols.String && (this.syData as string) === 'hide') {
3667+
tempoAutomation.isVisible = false;
3668+
this.sy = this.newSy();
3669+
}
3670+
3671+
if (this.sy !== AlphaTexSymbols.RParensis) {
3672+
this.error('tempo', AlphaTexSymbols.RParensis, true);
36493673
}
36503674
this.sy = this.newSy();
36513675
} else if (this.sy === AlphaTexSymbols.Number) {

packages/alphatab/src/importer/CapellaParser.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export class CapellaParser {
112112

113113
private _voiceCounts!: Map<number /*track*/, number /*count*/>;
114114
private _isFirstSystem: boolean = true;
115+
private _initialTempo: number = -1;
115116

116117
public parseXml(xml: string, settings: Settings): void {
117118
this._galleryObjects = new Map<string, DrawObject>();
@@ -177,7 +178,6 @@ export class CapellaParser {
177178
}
178179
if (root.localName === 'score') {
179180
this.score = new Score();
180-
this.score.tempo = 120;
181181
// parse all children
182182
for (const n of root.childElements()) {
183183
switch (n.localName) {
@@ -374,7 +374,7 @@ export class CapellaParser {
374374
private parseSystem(element: XmlNode) {
375375
if (element.attributes.has('tempo')) {
376376
if (this.score.masterBars.length === 0) {
377-
this.score.tempo = Number.parseInt(element.attributes.get('tempo')!, 10);
377+
this._initialTempo = Number.parseInt(element.attributes.get('tempo')!, 10);
378378
}
379379
}
380380

@@ -488,7 +488,6 @@ export class CapellaParser {
488488
currentBar.clefOttava = staff.bars[staff.bars.length - 1].clefOttava;
489489
currentBar.keySignature = staff.bars[staff.bars.length - 1].keySignature;
490490
currentBar.keySignatureType = staff.bars[staff.bars.length - 1].keySignatureType;
491-
492491
} else {
493492
currentBar.clef = this._currentStaffLayout.defaultClef;
494493
}
@@ -500,6 +499,8 @@ export class CapellaParser {
500499
this.score.addMasterBar(master);
501500
if (master.index > 0) {
502501
master.tripletFeel = master.previousMasterBar!.tripletFeel;
502+
} else if (this._initialTempo > 0) {
503+
master.tempoAutomations.push(Automation.buildTempoAutomation(false, 0, this._initialTempo, 0));
503504
}
504505

505506
master.timeSignatureDenominator = this._timeSignature.timeSignatureDenominator;
@@ -588,9 +589,7 @@ export class CapellaParser {
588589
this._currentBar.clefOttava = this.parseClefOttava(c.getAttribute('clef'));
589590
break;
590591
case 'keySign':
591-
this._currentBar.keySignature = Number.parseInt(
592-
c.getAttribute('fifths'), 10
593-
) as KeySignature;
592+
this._currentBar.keySignature = Number.parseInt(c.getAttribute('fifths'), 10) as KeySignature;
594593
break;
595594
case 'timeSign':
596595
this.parseTime(c.getAttribute('time'));

packages/alphatab/src/importer/Gp3To5Importer.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export class Gp3To5Importer extends ScoreImporter {
6767
private _beatTextChunksByTrack: Map<number, string[]> = new Map<number, string[]>();
6868

6969
private _directionLookup: Map<number, Direction[]> = new Map<number, Direction[]>();
70+
private _initialTempo: Automation | undefined;
7071

7172
public get name(): string {
7273
return 'Guitar Pro 3-5';
@@ -98,12 +99,13 @@ export class Gp3To5Importer extends ScoreImporter {
9899
this.data.skip(19);
99100
}
100101
// page setup since GP5
102+
this._initialTempo = Automation.buildTempoAutomation(false, 0, 0, 0);
101103
if (this._versionNumber >= 500) {
102104
this.readPageSetup();
103-
this._score.tempoLabel = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
105+
this._initialTempo.text = GpBinaryHelpers.gpReadStringIntByte(this.data, this.settings.importer.encoding);
104106
}
105107
// tempo stuff
106-
this._score.tempo = IOHelper.readInt32LE(this.data);
108+
this._initialTempo.value = IOHelper.readInt32LE(this.data);
107109
if (this._versionNumber >= 510) {
108110
GpBinaryHelpers.gpReadBool(this.data); // hide tempo?
109111
}
@@ -192,7 +194,8 @@ export class Gp3To5Importer extends ScoreImporter {
192194
}
193195
version = version.substr(Gp3To5Importer.VersionString.length + 1);
194196
const dot: number = version.indexOf(String.fromCharCode(46));
195-
this._versionNumber = 100 * Number.parseInt(version.substr(0, dot), 10) + Number.parseInt(version.substr(dot + 1), 10);
197+
this._versionNumber =
198+
100 * Number.parseInt(version.substr(0, dot), 10) + Number.parseInt(version.substr(dot + 1), 10);
196199
Logger.debug(this.name, `Guitar Pro version ${version} detected`);
197200
}
198201

@@ -317,6 +320,10 @@ export class Gp3To5Importer extends ScoreImporter {
317320
previousMasterBar = this._score.masterBars[this._score.masterBars.length - 1];
318321
}
319322
const newMasterBar: MasterBar = new MasterBar();
323+
if (!previousMasterBar && this._initialTempo!.value > 0) {
324+
newMasterBar.tempoAutomations.push(this._initialTempo!);
325+
}
326+
320327
const flags: number = this.data.readByte();
321328
// time signature
322329
if ((flags & 0x01) !== 0) {

packages/alphatab/src/importer/GpifParser.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ export class GpifParser {
441441
let reference: number = 0;
442442
let text: string | null = null;
443443
let syncPointValue: SyncPointData | undefined = undefined;
444+
let isVisible = true;
444445

445446
for (const c of node.childElements()) {
446447
switch (c.localName) {
@@ -495,6 +496,9 @@ export class GpifParser {
495496
case 'Text':
496497
text = c.innerText;
497498
break;
499+
case 'Visible':
500+
isVisible = c.innerText.toLowerCase() === 'true';
501+
break;
498502
}
499503
}
500504
if (!type) {
@@ -503,14 +507,17 @@ export class GpifParser {
503507
const newAutomations: Automation[] = [];
504508
switch (type) {
505509
case 'Tempo':
506-
newAutomations.push(Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference));
510+
newAutomations.push(
511+
Automation.buildTempoAutomation(isLinear, ratioPosition, numberValue, reference, isVisible)
512+
);
507513
break;
508514
case 'SyncPoint':
509515
const syncPoint = new Automation();
510516
syncPoint.type = AutomationType.SyncPoint;
511517
syncPoint.isLinear = isLinear;
512518
syncPoint.ratioPosition = ratioPosition;
513519
syncPoint.syncPointValue = syncPointValue;
520+
syncPoint.isVisible = isVisible;
514521
newAutomations.push(syncPoint);
515522
break;
516523
case 'Sound':
@@ -519,6 +526,7 @@ export class GpifParser {
519526
bankChange.type = AutomationType.Bank;
520527
bankChange.ratioPosition = ratioPosition;
521528
bankChange.value = sounds.get(textValue)!.bank;
529+
bankChange.isVisible = isVisible;
522530
newAutomations.push(bankChange);
523531

524532
const programChange = Automation.buildInstrumentAutomation(
@@ -2821,12 +2829,6 @@ export class GpifParser {
28212829
const automation: Automation = automations[i];
28222830
switch (automation.type) {
28232831
case AutomationType.Tempo:
2824-
if (barNumber === 0) {
2825-
this.score.tempo = automation.value | 0;
2826-
if (automation.text) {
2827-
this.score.tempoLabel = automation.text;
2828-
}
2829-
}
28302832
masterBar.tempoAutomations.push(automation);
28312833
break;
28322834
case AutomationType.SyncPoint:

packages/alphatab/src/importer/MusicXmlImporter.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ export class MusicXmlImporter extends ScoreImporter {
198198
throw new UnsupportedFormatError('Unsupported format', e as Error);
199199
}
200200
this._score = new Score();
201-
this._score.tempo = 120;
202201
this._score.stylesheet.hideDynamics = true;
203202

204203
this.parseDom(dom);
@@ -1995,9 +1994,6 @@ export class MusicXmlImporter extends ScoreImporter {
19951994

19961995
if (!this.hasSameTempo(masterBar, tempoAutomation)) {
19971996
masterBar.tempoAutomations.push(tempoAutomation);
1998-
if (masterBar.index === 0) {
1999-
masterBar.score.tempo = tempoAutomation.value;
2000-
}
20011997
}
20021998
}
20031999

@@ -2153,9 +2149,6 @@ export class MusicXmlImporter extends ScoreImporter {
21532149

21542150
if (!this.hasSameTempo(masterBar, tempoAutomation)) {
21552151
masterBar.tempoAutomations.push(tempoAutomation);
2156-
if (masterBar.index === 0) {
2157-
masterBar.score.tempo = tempoAutomation.value;
2158-
}
21592152
}
21602153
}
21612154
}

0 commit comments

Comments
 (0)