@@ -37,6 +37,7 @@ import { BeatCloner } from '@src/generated/model/BeatCloner';
37
37
import { IOHelper } from '@src/io/IOHelper' ;
38
38
import { Settings } from '@src/Settings' ;
39
39
import { ByteBuffer } from '@src/io/ByteBuffer' ;
40
+ import { PercussionMapper } from '@src/model/PercussionMapper' ;
40
41
41
42
/**
42
43
* A list of terminals recognized by the alphaTex-parser
@@ -56,7 +57,7 @@ export enum AlphaTexSymbols {
56
57
Pipe ,
57
58
MetaCommand ,
58
59
Multiply ,
59
- LowerThan ,
60
+ LowerThan
60
61
}
61
62
62
63
export class AlphaTexError extends AlphaTabError {
@@ -76,7 +77,7 @@ export class AlphaTexError extends AlphaTabError {
76
77
nonTerm : string | null ,
77
78
expected : AlphaTexSymbols | null ,
78
79
symbol : AlphaTexSymbols | null ,
79
- symbolData : unknown = null ,
80
+ symbolData : unknown = null
80
81
) {
81
82
super ( AlphaTabErrorType . AlphaTex , message ) ;
82
83
this . position = position ;
@@ -96,7 +97,7 @@ export class AlphaTexError extends AlphaTabError {
96
97
nonTerm : string ,
97
98
expected : AlphaTexSymbols ,
98
99
symbol : AlphaTexSymbols ,
99
- symbolData : unknown = null ,
100
+ symbolData : unknown = null
100
101
) : AlphaTexError {
101
102
let message = `MalFormed AlphaTex: @${ position } (line ${ line } , col ${ col } ): Error on block ${ nonTerm } ` ;
102
103
if ( expected !== symbol ) {
@@ -125,7 +126,7 @@ export class AlphaTexImporter extends ScoreImporter {
125
126
private _score ! : Score ;
126
127
private _currentTrack ! : Track ;
127
128
private _currentStaff ! : Staff ;
128
- private _input : string = "" ;
129
+ private _input : string = '' ;
129
130
private _ch : number = AlphaTexImporter . Eof ;
130
131
// Keeps track of where in input string we are
131
132
private _curChPos : number = 0 ;
@@ -134,7 +135,7 @@ export class AlphaTexImporter extends ScoreImporter {
134
135
// Last known position that had valid syntax/symbols
135
136
private _lastValidSpot : number [ ] = [ 0 , 1 , 0 ] ;
136
137
private _sy : AlphaTexSymbols = AlphaTexSymbols . No ;
137
- private _syData : unknown = "" ;
138
+ private _syData : unknown = '' ;
138
139
private _allowNegatives : boolean = false ;
139
140
private _allowFloat : boolean = false ;
140
141
private _allowTuning : boolean = false ;
@@ -145,6 +146,7 @@ export class AlphaTexImporter extends ScoreImporter {
145
146
146
147
private _staffHasExplicitTuning : boolean = false ;
147
148
private _staffTuningApplied : boolean = false ;
149
+ private _percussionArticulationNames = new Map < string , number > ( ) ;
148
150
149
151
public logErrors : boolean = false ;
150
152
@@ -613,11 +615,8 @@ export class AlphaTexImporter extends ScoreImporter {
613
615
*/
614
616
private static isNameLetter ( ch : number ) : boolean {
615
617
return (
616
- ! AlphaTexImporter . isTerminal ( ch ) && ( // no control characters, whitespaces, numbers or dots
617
- ( 0x21 <= ch && ch <= 0x2f ) ||
618
- ( 0x3a <= ch && ch <= 0x7e ) ||
619
- 0x80 <= ch // Unicode Symbols
620
- )
618
+ ! AlphaTexImporter . isTerminal ( ch ) && // no control characters, whitespaces, numbers or dots
619
+ ( ( 0x21 <= ch && ch <= 0x2f ) || ( 0x3a <= ch && ch <= 0x7e ) || 0x80 <= ch ) // Unicode Symbols
621
620
) ;
622
621
}
623
622
@@ -650,8 +649,8 @@ export class AlphaTexImporter extends ScoreImporter {
650
649
private isDigit ( ch : number ) : boolean {
651
650
return (
652
651
( ch >= 0x30 && ch <= 0x39 ) /* 0-9 */ ||
653
- ( this . _allowNegatives && ch === 0x2d /* - */ ) || // allow minus sign if negatives
654
- ( this . _allowFloat && ch === 0x2e /* . */ ) // allow dot if float
652
+ ( this . _allowNegatives && ch === 0x2d ) /* - */ || // allow minus sign if negatives
653
+ ( this . _allowFloat && ch === 0x2e ) /* . */ // allow dot if float
655
654
) ;
656
655
}
657
656
@@ -821,7 +820,15 @@ export class AlphaTexImporter extends ScoreImporter {
821
820
}
822
821
} else if ( this . _sy === AlphaTexSymbols . String ) {
823
822
let instrumentName : string = ( this . _syData as string ) . toLowerCase ( ) ;
824
- this . _currentTrack . playbackInfo . program = GeneralMidi . getValue ( instrumentName ) ;
823
+ if ( instrumentName === 'percussion' ) {
824
+ for ( const staff of this . _currentTrack . staves ) {
825
+ this . applyPercussionStaff ( staff ) ;
826
+ }
827
+ this . _currentTrack . playbackInfo . primaryChannel = 9 ;
828
+ this . _currentTrack . playbackInfo . secondaryChannel = 9 ;
829
+ } else {
830
+ this . _currentTrack . playbackInfo . program = GeneralMidi . getValue ( instrumentName ) ;
831
+ }
825
832
} else {
826
833
this . error ( 'instrument' , AlphaTexSymbols . Number , true ) ;
827
834
}
@@ -852,7 +859,7 @@ export class AlphaTexImporter extends ScoreImporter {
852
859
chord . name = this . _syData as string ;
853
860
this . _sy = this . newSy ( ) ;
854
861
} else {
855
- this . error ( 'chord-name' , AlphaTexSymbols . Number , true ) ;
862
+ this . error ( 'chord-name' , AlphaTexSymbols . String , true ) ;
856
863
}
857
864
for ( let i : number = 0 ; i < this . _currentStaff . tuning . length ; i ++ ) {
858
865
if ( this . _sy === AlphaTexSymbols . Number ) {
@@ -864,10 +871,57 @@ export class AlphaTexImporter extends ScoreImporter {
864
871
}
865
872
this . _currentStaff . addChord ( this . getChordId ( this . _currentStaff , chord . name ) , chord ) ;
866
873
return true ;
874
+ case 'articulation' :
875
+ this . _sy = this . newSy ( ) ;
876
+
877
+ let name = '' ;
878
+ if ( this . _sy === AlphaTexSymbols . String ) {
879
+ name = this . _syData as string ;
880
+ this . _sy = this . newSy ( ) ;
881
+ } else {
882
+ this . error ( 'articulation-name' , AlphaTexSymbols . String , true ) ;
883
+ }
884
+
885
+ if ( name === 'defaults' ) {
886
+ for ( const [ defaultName , defaultValue ] of PercussionMapper . instrumentArticulationNames ) {
887
+ this . _percussionArticulationNames . set ( defaultName . toLowerCase ( ) , defaultValue ) ;
888
+ this . _percussionArticulationNames . set ( AlphaTexImporter . toArticulationId ( defaultName ) , defaultValue ) ;
889
+ }
890
+ return true ;
891
+ }
892
+
893
+ let number = 0 ;
894
+ if ( this . _sy === AlphaTexSymbols . Number ) {
895
+ number = this . _syData as number ;
896
+ this . _sy = this . newSy ( ) ;
897
+ } else {
898
+ this . error ( 'articulation-number' , AlphaTexSymbols . Number , true ) ;
899
+ }
900
+
901
+ if ( ! PercussionMapper . instrumentArticulations . has ( number ) ) {
902
+ this . errorMessage (
903
+ `Unknown articulation ${ number } . Refer to https://www.alphatab.net/docs/alphatex/percussion for available ids`
904
+ ) ;
905
+ }
906
+
907
+ this . _percussionArticulationNames . set ( name . toLowerCase ( ) , number ) ;
908
+ return true ;
867
909
default :
868
910
return false ;
869
911
}
870
912
}
913
+
914
+ /**
915
+ * Encodes a given string to a shorthand text form without spaces or special characters
916
+ */
917
+ private static toArticulationId ( plain : string ) : string {
918
+ return plain . replace ( new RegExp ( "[^a-zA-Z0-9]" , "g" ) , "" ) . toLowerCase ( )
919
+ }
920
+
921
+ private applyPercussionStaff ( staff : Staff ) {
922
+ staff . isPercussion = true ;
923
+ staff . showTablature = false ;
924
+ }
871
925
872
926
private chordProperties ( chord : Chord ) : void {
873
927
if ( this . _sy !== AlphaTexSymbols . LBrace ) {
@@ -998,7 +1052,14 @@ export class AlphaTexImporter extends ScoreImporter {
998
1052
this . _sy = this . newSy ( ) ;
999
1053
if ( this . _currentTrack . staves [ 0 ] . bars . length > 0 ) {
1000
1054
this . _currentTrack . ensureStaveCount ( this . _currentTrack . staves . length + 1 ) ;
1055
+
1056
+ const isPercussion = this . _currentStaff . isPercussion ;
1001
1057
this . _currentStaff = this . _currentTrack . staves [ this . _currentTrack . staves . length - 1 ] ;
1058
+
1059
+ if ( isPercussion ) {
1060
+ this . applyPercussionStaff ( this . _currentStaff ) ;
1061
+ }
1062
+
1002
1063
this . _currentDynamics = DynamicValue . F ;
1003
1064
}
1004
1065
this . staffProperties ( ) ;
@@ -1070,7 +1131,15 @@ export class AlphaTexImporter extends ScoreImporter {
1070
1131
// bass G2 D2 A1 E1
1071
1132
this . _currentStaff . displayTranspositionPitch = - 12 ;
1072
1133
this . _currentStaff . stringTuning . tunings = [ 43 , 38 , 33 , 28 ] ;
1073
- } else if ( program == 40 || program == 44 || program == 45 || program == 48 || program == 49 || program == 50 || program == 51 ) {
1134
+ } else if (
1135
+ program == 40 ||
1136
+ program == 44 ||
1137
+ program == 45 ||
1138
+ program == 48 ||
1139
+ program == 49 ||
1140
+ program == 50 ||
1141
+ program == 51
1142
+ ) {
1074
1143
// violin E3 A3 D3 G2
1075
1144
this . _currentStaff . stringTuning . tunings = [ 52 , 57 , 50 , 43 ] ;
1076
1145
} else if ( program == 41 ) {
@@ -1134,15 +1203,20 @@ export class AlphaTexImporter extends ScoreImporter {
1134
1203
}
1135
1204
1136
1205
private beat ( voice : Voice ) : boolean {
1137
- // duration specifier?
1206
+ // duration specifier?
1138
1207
this . beatDuration ( ) ;
1208
+
1139
1209
let beat : Beat = new Beat ( ) ;
1140
1210
voice . addBeat ( beat ) ;
1211
+
1212
+ this . _allowTuning = ! this . _currentStaff . isPercussion ;
1213
+
1141
1214
// notes
1142
1215
if ( this . _sy === AlphaTexSymbols . LParensis ) {
1143
1216
this . _sy = this . newSy ( ) ;
1144
1217
this . note ( beat ) ;
1145
1218
while ( this . _sy !== AlphaTexSymbols . RParensis && this . _sy !== AlphaTexSymbols . Eof ) {
1219
+ this . _allowTuning = ! this . _currentStaff . isPercussion ;
1146
1220
if ( ! this . note ( beat ) ) {
1147
1221
break ;
1148
1222
}
@@ -1510,15 +1584,27 @@ export class AlphaTexImporter extends ScoreImporter {
1510
1584
switch ( this . _sy ) {
1511
1585
case AlphaTexSymbols . Number :
1512
1586
fret = this . _syData as number ;
1587
+ if ( this . _currentStaff . isPercussion && ! PercussionMapper . instrumentArticulations . has ( fret ) ) {
1588
+ this . errorMessage ( `Unknown percussion articulation ${ fret } ` ) ;
1589
+ }
1513
1590
break ;
1514
1591
case AlphaTexSymbols . String :
1515
- isDead = ( this . _syData as string ) === 'x' ;
1516
- isTie = ( this . _syData as string ) === '-' ;
1517
-
1518
- if ( isTie || isDead ) {
1519
- fret = 0 ;
1592
+ if ( this . _currentStaff . isPercussion ) {
1593
+ const articulationName = ( this . _syData as string ) . toLowerCase ( ) ;
1594
+ if ( this . _percussionArticulationNames . has ( articulationName ) ) {
1595
+ fret = this . _percussionArticulationNames . get ( articulationName ) ! ;
1596
+ } else {
1597
+ this . errorMessage ( `Unknown percussion articulation '${ this . _syData } '` ) ;
1598
+ }
1520
1599
} else {
1521
- this . error ( 'note-fret' , AlphaTexSymbols . Number , true ) ;
1600
+ isDead = ( this . _syData as string ) === 'x' ;
1601
+ isTie = ( this . _syData as string ) === '-' ;
1602
+
1603
+ if ( isTie || isDead ) {
1604
+ fret = 0 ;
1605
+ } else {
1606
+ this . error ( 'note-fret' , AlphaTexSymbols . Number , true ) ;
1607
+ }
1522
1608
}
1523
1609
break ;
1524
1610
case AlphaTexSymbols . Tuning :
@@ -1531,7 +1617,8 @@ export class AlphaTexImporter extends ScoreImporter {
1531
1617
}
1532
1618
this . _sy = this . newSy ( ) ; // Fret done
1533
1619
1534
- let isFretted : boolean = octave === - 1 && this . _currentStaff . tuning . length > 0 ;
1620
+ let isFretted : boolean =
1621
+ octave === - 1 && this . _currentStaff . tuning . length > 0 && ! this . _currentStaff . isPercussion ;
1535
1622
let noteString : number = - 1 ;
1536
1623
if ( isFretted ) {
1537
1624
// Fret [Dot] String
@@ -1558,6 +1645,8 @@ export class AlphaTexImporter extends ScoreImporter {
1558
1645
if ( ! isTie ) {
1559
1646
note . fret = fret ;
1560
1647
}
1648
+ } else if ( this . _currentStaff . isPercussion ) {
1649
+ note . percussionArticulation = fret ;
1561
1650
} else {
1562
1651
note . octave = octave ;
1563
1652
note . tone = tone ;
0 commit comments