Skip to content

Commit 52bfad1

Browse files
authored
feat: add buzzroll tremolos (#2476)
1 parent 05397ad commit 52bfad1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+933
-7961
lines changed

packages/alphatab/scripts/smufl-metadata.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ const metadata: SmuflMetadata = JSON.parse(await fs.promises.readFile(input, 'ut
1212

1313
const outputMetadata:SmuflMetadata = {
1414
engravingDefaults: metadata.engravingDefaults,
15-
glyphBBoxes: {},
16-
glyphsWithAnchors: {}
15+
glyphBBoxes: {} as SmuflMetadata['glyphBBoxes'],
16+
glyphsWithAnchors: {} as SmuflMetadata['glyphsWithAnchors']
1717
};
1818

1919
const alphaTabUsedGlyphs = new Set<string>();

packages/alphatab/src/EngravingSettings.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,10 @@ export class EngravingSettings {
928928
bBoxNE: [1.876, 1.18],
929929
bBoxSW: [0, 0]
930930
},
931+
buzzRoll: {
932+
bBoxNE: [0.624, 0.464],
933+
bBoxSW: [-0.62, -0.464]
934+
},
931935
cClef: {
932936
bBoxNE: [2.796, 2.024],
933937
bBoxSW: [0, -2.024]
@@ -1884,6 +1888,14 @@ export class EngravingSettings {
18841888
bBoxNE: [0.6, 1.112],
18851889
bBoxSW: [-0.6, -1.12]
18861890
},
1891+
tremolo4: {
1892+
bBoxNE: [0.6, 1.496],
1893+
bBoxSW: [-0.6, -1.48]
1894+
},
1895+
tremolo5: {
1896+
bBoxNE: [0.6, 1.88],
1897+
bBoxSW: [-0.604, -1.84]
1898+
},
18871899
tuplet0: {
18881900
bBoxNE: [1.2731041262817027, 1.5],
18891901
bBoxSW: [-0.001204330173715796, -0.032]

packages/alphatab/src/exporter/GpifWriter.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -792,16 +792,17 @@ export class GpifWriter {
792792
beatNode.addElement('Fadding').innerText = FadeType[beat.fade];
793793
}
794794
if (beat.isTremolo) {
795-
switch (beat.tremoloSpeed) {
796-
case Duration.Eighth:
795+
switch (beat.tremoloPicking!.marks) {
796+
case 1:
797797
beatNode.addElement('Tremolo').innerText = '1/2';
798798
break;
799-
case Duration.Sixteenth:
799+
case 2:
800800
beatNode.addElement('Tremolo').innerText = '1/4';
801801
break;
802-
case Duration.ThirtySecond:
802+
case 3:
803803
beatNode.addElement('Tremolo').innerText = '1/8';
804804
break;
805+
// NOTE: guitar pro does not support other tremolos
805806
}
806807
}
807808
if (beat.hasChord) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Beat } from "@coderline/alphatab/model/Beat";
77
import { NoteCloner } from "@coderline/alphatab/generated/model/NoteCloner";
88
import { AutomationCloner } from "@coderline/alphatab/generated/model/AutomationCloner";
99
import { BendPointCloner } from "@coderline/alphatab/generated/model/BendPointCloner";
10+
import { TremoloPickingEffectCloner } from "@coderline/alphatab/generated/model/TremoloPickingEffectCloner";
1011
/**
1112
* @internal
1213
*/
@@ -54,7 +55,7 @@ export class BeatCloner {
5455
clone.chordId = original.chordId;
5556
clone.graceType = original.graceType;
5657
clone.pickStroke = original.pickStroke;
57-
clone.tremoloSpeed = original.tremoloSpeed;
58+
clone.tremoloPicking = original.tremoloPicking ? TremoloPickingEffectCloner.clone(original.tremoloPicking) : undefined;
5859
clone.crescendo = original.crescendo;
5960
clone.displayStart = original.displayStart;
6061
clone.playbackStart = original.playbackStart;

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { JsonHelper } from "@coderline/alphatab/io/JsonHelper";
88
import { NoteSerializer } from "@coderline/alphatab/generated/model/NoteSerializer";
99
import { AutomationSerializer } from "@coderline/alphatab/generated/model/AutomationSerializer";
1010
import { BendPointSerializer } from "@coderline/alphatab/generated/model/BendPointSerializer";
11+
import { TremoloPickingEffectSerializer } from "@coderline/alphatab/generated/model/TremoloPickingEffectSerializer";
1112
import { BeatStyleSerializer } from "@coderline/alphatab/generated/model/BeatStyleSerializer";
1213
import { Note } from "@coderline/alphatab/model/Note";
1314
import { BendStyle } from "@coderline/alphatab/model/BendStyle";
@@ -21,6 +22,7 @@ import { BendPoint } from "@coderline/alphatab/model/BendPoint";
2122
import { VibratoType } from "@coderline/alphatab/model/VibratoType";
2223
import { GraceType } from "@coderline/alphatab/model/GraceType";
2324
import { PickStroke } from "@coderline/alphatab/model/PickStroke";
25+
import { TremoloPickingEffect } from "@coderline/alphatab/model/TremoloPickingEffect";
2426
import { CrescendoType } from "@coderline/alphatab/model/CrescendoType";
2527
import { GolpeType } from "@coderline/alphatab/model/GolpeType";
2628
import { DynamicValue } from "@coderline/alphatab/model/DynamicValue";
@@ -75,7 +77,9 @@ export class BeatSerializer {
7577
o.set("chordid", obj.chordId);
7678
o.set("gracetype", obj.graceType as number);
7779
o.set("pickstroke", obj.pickStroke as number);
78-
o.set("tremolospeed", obj.tremoloSpeed as number | null);
80+
if (obj.tremoloPicking) {
81+
o.set("tremolopicking", TremoloPickingEffectSerializer.toJson(obj.tremoloPicking));
82+
}
7983
o.set("crescendo", obj.crescendo as number);
8084
o.set("displaystart", obj.displayStart);
8185
o.set("playbackstart", obj.playbackStart);
@@ -201,8 +205,14 @@ export class BeatSerializer {
201205
case "pickstroke":
202206
obj.pickStroke = JsonHelper.parseEnum<PickStroke>(v, PickStroke)!;
203207
return true;
204-
case "tremolospeed":
205-
obj.tremoloSpeed = JsonHelper.parseEnum<Duration>(v, Duration) ?? null;
208+
case "tremolopicking":
209+
if (v) {
210+
obj.tremoloPicking = new TremoloPickingEffect();
211+
TremoloPickingEffectSerializer.fromJson(obj.tremoloPicking, v);
212+
}
213+
else {
214+
obj.tremoloPicking = undefined;
215+
}
206216
return true;
207217
case "crescendo":
208218
obj.crescendo = JsonHelper.parseEnum<CrescendoType>(v, CrescendoType)!;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// <auto-generated>
2+
// This code was auto-generated.
3+
// Changes to this file may cause incorrect behavior and will be lost if
4+
// the code is regenerated.
5+
// </auto-generated>
6+
import { TremoloPickingEffect } from "@coderline/alphatab/model/TremoloPickingEffect";
7+
/**
8+
* @internal
9+
*/
10+
export class TremoloPickingEffectCloner {
11+
public static clone(original: TremoloPickingEffect): TremoloPickingEffect {
12+
const clone = new TremoloPickingEffect();
13+
clone.marks = original.marks;
14+
clone.style = original.style;
15+
return clone;
16+
}
17+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// <auto-generated>
2+
// This code was auto-generated.
3+
// Changes to this file may cause incorrect behavior and will be lost if
4+
// the code is regenerated.
5+
// </auto-generated>
6+
import { TremoloPickingEffect } from "@coderline/alphatab/model/TremoloPickingEffect";
7+
import { JsonHelper } from "@coderline/alphatab/io/JsonHelper";
8+
import { TremoloPickingStyle } from "@coderline/alphatab/model/TremoloPickingEffect";
9+
/**
10+
* @internal
11+
*/
12+
export class TremoloPickingEffectSerializer {
13+
public static fromJson(obj: TremoloPickingEffect, m: unknown): void {
14+
if (!m) {
15+
return;
16+
}
17+
JsonHelper.forEach(m, (v, k) => TremoloPickingEffectSerializer.setProperty(obj, k, v));
18+
}
19+
public static toJson(obj: TremoloPickingEffect | null): Map<string, unknown> | null {
20+
if (!obj) {
21+
return null;
22+
}
23+
const o = new Map<string, unknown>();
24+
o.set("marks", obj.marks);
25+
o.set("style", obj.style as number);
26+
return o;
27+
}
28+
public static setProperty(obj: TremoloPickingEffect, property: string, v: unknown): boolean {
29+
switch (property) {
30+
case "marks":
31+
obj.marks = v! as number;
32+
return true;
33+
case "style":
34+
obj.style = JsonHelper.parseEnum<TremoloPickingStyle>(v, TremoloPickingStyle)!;
35+
return true;
36+
}
37+
return false;
38+
}
39+
}

packages/alphatab/src/importer/Gp3To5Importer.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection
4747
import { Ottavia } from '@coderline/alphatab/model/Ottavia';
4848
import { WahPedal } from '@coderline/alphatab/model/WahPedal';
4949
import { AccidentalType } from '@coderline/alphatab/model/AccidentalType';
50+
import { TremoloPickingEffect } from '@coderline/alphatab/model/TremoloPickingEffect';
5051

5152
/**
5253
* @internal
@@ -63,7 +64,10 @@ export class Gp3To5Importer extends ScoreImporter {
6364
private _playbackInfos: PlaybackInformation[] = [];
6465
private _doubleBars: Set<number> = new Set<number>();
6566
private _clefsPerTrack: Map<number, Clef> = new Map<number, Clef>();
66-
private _keySignatures: Map<number, [KeySignature, KeySignatureType]> = new Map<number, [KeySignature, KeySignatureType]>();
67+
private _keySignatures: Map<number, [KeySignature, KeySignatureType]> = new Map<
68+
number,
69+
[KeySignature, KeySignatureType]
70+
>();
6771
private _beatTextChunksByTrack: Map<number, string[]> = new Map<number, string[]>();
6872

6973
private _directionLookup: Map<number, Direction[]> = new Map<number, Direction[]>();
@@ -1356,18 +1360,9 @@ export class Gp3To5Importer extends ScoreImporter {
13561360
}
13571361

13581362
public readTremoloPicking(beat: Beat): void {
1359-
const speed: number = this.data.readByte();
1360-
switch (speed) {
1361-
case 1:
1362-
beat.tremoloSpeed = Duration.Eighth;
1363-
break;
1364-
case 2:
1365-
beat.tremoloSpeed = Duration.Sixteenth;
1366-
break;
1367-
case 3:
1368-
beat.tremoloSpeed = Duration.ThirtySecond;
1369-
break;
1370-
}
1363+
const effect = new TremoloPickingEffect();
1364+
beat.tremoloPicking = effect;
1365+
effect.marks = this.data.readByte();
13711366
}
13721367

13731368
public readSlide(note: Note): void {

packages/alphatab/src/importer/GpifParser.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import { Direction } from '@coderline/alphatab/model/Direction';
5555
import { ModelUtils } from '@coderline/alphatab/model/ModelUtils';
5656
import { BackingTrack } from '@coderline/alphatab/model/BackingTrack';
5757
import { Tuning } from '@coderline/alphatab/model/Tuning';
58+
import { TremoloPickingEffect } from '@coderline/alphatab/model/TremoloPickingEffect';
5859

5960
/**
6061
* This structure represents a duration within a gpif
@@ -1693,15 +1694,17 @@ export class GpifParser {
16931694
}
16941695
break;
16951696
case 'Tremolo':
1697+
const tremolo = new TremoloPickingEffect();
1698+
beat.tremoloPicking = tremolo;
16961699
switch (c.innerText) {
16971700
case '1/2':
1698-
beat.tremoloSpeed = Duration.Eighth;
1701+
tremolo.marks = 1;
16991702
break;
17001703
case '1/4':
1701-
beat.tremoloSpeed = Duration.Sixteenth;
1704+
tremolo.marks = 2;
17021705
break;
17031706
case '1/8':
1704-
beat.tremoloSpeed = Duration.ThirtySecond;
1707+
tremolo.marks = 3;
17051708
break;
17061709
}
17071710
break;

packages/alphatab/src/importer/MusicXmlImporter.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { SimileMark } from '@coderline/alphatab/model/SimileMark';
3636
import { SlideOutType } from '@coderline/alphatab/model/SlideOutType';
3737
import { Staff } from '@coderline/alphatab/model/Staff';
3838
import { Track } from '@coderline/alphatab/model/Track';
39+
import { TremoloPickingEffect, TremoloPickingStyle } from '@coderline/alphatab/model/TremoloPickingEffect';
3940
import { TripletFeel } from '@coderline/alphatab/model/TripletFeel';
4041
import { VibratoType } from '@coderline/alphatab/model/VibratoType';
4142
import { Voice } from '@coderline/alphatab/model/Voice';
@@ -3579,17 +3580,17 @@ export class MusicXmlImporter extends ScoreImporter {
35793580
break;
35803581
// case 'schleifer': Not supported
35813582
case 'tremolo':
3582-
switch (c.innerText) {
3583-
case '1':
3584-
note.beat.tremoloSpeed = Duration.Eighth;
3585-
break;
3586-
case '2':
3587-
note.beat.tremoloSpeed = Duration.Sixteenth;
3588-
break;
3589-
case '3':
3590-
note.beat.tremoloSpeed = Duration.ThirtySecond;
3591-
break;
3583+
const tremolo = new TremoloPickingEffect();
3584+
note.beat.tremoloPicking = tremolo;
3585+
tremolo.marks = Number.parseInt(c.innerText, 10);
3586+
3587+
if (
3588+
(c.getAttribute('type', '') === 'unmeasured' && tremolo.marks === 0) ||
3589+
c.getAttribute('smufl', '') === 'buzzRoll'
3590+
) {
3591+
tremolo.style = TremoloPickingStyle.BuzzRoll;
35923592
}
3593+
35933594
break;
35943595
// case 'haydn': Not supported
35953596
// case 'other-element': Not supported

0 commit comments

Comments
 (0)