@k-l-lambda/lilylet 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/lib/grammar.jison.js +1263 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/meiEncoder.d.ts +8 -0
- package/lib/meiEncoder.js +1386 -0
- package/lib/musicXmlDecoder.d.ts +20 -0
- package/lib/musicXmlDecoder.js +1047 -0
- package/lib/musicXmlTypes.d.ts +199 -0
- package/lib/musicXmlTypes.js +7 -0
- package/lib/musicXmlUtils.d.ts +81 -0
- package/lib/musicXmlUtils.js +435 -0
- package/lib/parser.d.ts +3 -0
- package/lib/parser.js +151 -0
- package/lib/serializer.d.ts +11 -0
- package/lib/serializer.js +578 -0
- package/lib/types.d.ts +241 -0
- package/lib/types.js +99 -0
- package/package.json +57 -0
- package/source/lilylet/grammar.jison.js +1263 -0
- package/source/lilylet/index.ts +12 -0
- package/source/lilylet/lilylet.jison +593 -0
- package/source/lilylet/lilypondDecoder.ts +789 -0
- package/source/lilylet/meiEncoder.ts +1780 -0
- package/source/lilylet/musicXmlDecoder.ts +1311 -0
- package/source/lilylet/musicXmlTypes.ts +205 -0
- package/source/lilylet/musicXmlUtils.ts +532 -0
- package/source/lilylet/parser.ts +178 -0
- package/source/lilylet/serializer.ts +748 -0
- package/source/lilylet/types.ts +304 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
|
|
2
|
+
%{
|
|
3
|
+
// Helper functions
|
|
4
|
+
const fraction = (numerator, denominator) => ({ numerator, denominator });
|
|
5
|
+
|
|
6
|
+
const pitch = (phonet, accidental, octave) => ({
|
|
7
|
+
phonet,
|
|
8
|
+
accidental: accidental || undefined,
|
|
9
|
+
octave: octave || 0,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const duration = (division, dots) => ({
|
|
13
|
+
division,
|
|
14
|
+
dots: dots || 0,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const noteEvent = (pitches, dur, marks, options = {}) => {
|
|
18
|
+
// Check if this is a pitched rest (e.g., g'\rest)
|
|
19
|
+
const pitchedRestMark = marks && marks.find(m => m && m.pitchedRest);
|
|
20
|
+
if (pitchedRestMark) {
|
|
21
|
+
const pitch = Array.isArray(pitches) ? pitches[0] : pitches;
|
|
22
|
+
return {
|
|
23
|
+
type: 'rest',
|
|
24
|
+
duration: dur,
|
|
25
|
+
pitch: pitch,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
type: 'note',
|
|
30
|
+
pitches: Array.isArray(pitches) ? pitches : [pitches],
|
|
31
|
+
duration: dur,
|
|
32
|
+
marks: marks && marks.length ? marks : undefined,
|
|
33
|
+
...options,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const restEvent = (dur, options = {}) => ({
|
|
38
|
+
type: 'rest',
|
|
39
|
+
duration: dur,
|
|
40
|
+
...options,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const contextChange = (changes) => ({
|
|
44
|
+
type: 'context',
|
|
45
|
+
...changes,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const keySignature = (phonet, accidental, mode) => ({
|
|
49
|
+
pitch: phonet,
|
|
50
|
+
accidental: accidental || undefined,
|
|
51
|
+
mode,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const voice = (staff, events) => ({
|
|
55
|
+
staff: staff || 1,
|
|
56
|
+
events,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const part = (voices, name) => ({
|
|
60
|
+
name: name || undefined,
|
|
61
|
+
voices,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const measure = (parts, key, timeSig, partial) => ({
|
|
65
|
+
key: key || undefined,
|
|
66
|
+
timeSig: timeSig || undefined,
|
|
67
|
+
parts,
|
|
68
|
+
partial: partial || undefined,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const tupletEvent = (ratio, events) => ({
|
|
72
|
+
type: 'tuplet',
|
|
73
|
+
ratio,
|
|
74
|
+
events,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const tremoloEvent = (pitchA, pitchB, count, division) => ({
|
|
78
|
+
type: 'tremolo',
|
|
79
|
+
pitchA,
|
|
80
|
+
pitchB,
|
|
81
|
+
count,
|
|
82
|
+
division,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Articulation/mark helpers - all marks have markType for discrimination
|
|
86
|
+
const articulation = (type, placement) => ({ markType: 'articulation', type, placement });
|
|
87
|
+
const ornament = (type) => ({ markType: 'ornament', type });
|
|
88
|
+
const dynamic = (type) => ({ markType: 'dynamic', type });
|
|
89
|
+
const hairpin = (type) => ({ markType: 'hairpin', type });
|
|
90
|
+
const pedal = (type) => ({ markType: 'pedal', type });
|
|
91
|
+
const tie = (start) => ({ markType: 'tie', start });
|
|
92
|
+
const slur = (start) => ({ markType: 'slur', start });
|
|
93
|
+
const beam = (start) => ({ markType: 'beam', start });
|
|
94
|
+
const fingering = (finger, placement) => ({ markType: 'fingering', finger, placement });
|
|
95
|
+
const navigation = (type) => ({ markType: 'navigation', type });
|
|
96
|
+
|
|
97
|
+
const barlineEvent = (style) => ({ type: 'barline', style });
|
|
98
|
+
const harmonyEvent = (text) => ({ type: 'harmony', text });
|
|
99
|
+
const markupEvent = (content, placement) => ({ type: 'markup', content, placement: placement || undefined });
|
|
100
|
+
const markupMark = (content) => ({ markType: 'markup', content });
|
|
101
|
+
|
|
102
|
+
// Parse PITCH token (e.g., "c", "cs", "bf", "css", "bff") into phonet and accidental
|
|
103
|
+
const parsePitch = (text, octave) => {
|
|
104
|
+
const phonet = text[0].toLowerCase();
|
|
105
|
+
const accStr = text.slice(1).toLowerCase();
|
|
106
|
+
let accidental = undefined;
|
|
107
|
+
if (accStr === 's') accidental = 'sharp';
|
|
108
|
+
else if (accStr === 'f') accidental = 'flat';
|
|
109
|
+
else if (accStr === 'ss') accidental = 'doubleSharp';
|
|
110
|
+
else if (accStr === 'ff') accidental = 'doubleFlat';
|
|
111
|
+
return pitch(phonet, accidental, octave || 0);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Parse PITCH token for key signature (no octave)
|
|
115
|
+
const parsePitchName = (text) => {
|
|
116
|
+
const phonet = text[0].toLowerCase();
|
|
117
|
+
const accStr = text.slice(1).toLowerCase();
|
|
118
|
+
let accidental = undefined;
|
|
119
|
+
if (accStr === 's') accidental = 'sharp';
|
|
120
|
+
else if (accStr === 'f') accidental = 'flat';
|
|
121
|
+
else if (accStr === 'ss') accidental = 'doubleSharp';
|
|
122
|
+
else if (accStr === 'ff') accidental = 'doubleFlat';
|
|
123
|
+
return { phonet, accidental };
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Global state for parsing
|
|
127
|
+
let currentStaff = 1;
|
|
128
|
+
let currentKey = null;
|
|
129
|
+
let currentTimeSig = null;
|
|
130
|
+
let currentDuration = { division: 4, dots: 0 }; // default quarter note
|
|
131
|
+
|
|
132
|
+
// Reset parser state - call before each parse
|
|
133
|
+
const resetParserState = () => {
|
|
134
|
+
currentStaff = 1;
|
|
135
|
+
currentKey = null;
|
|
136
|
+
currentTimeSig = null;
|
|
137
|
+
currentDuration = { division: 4, dots: 0 };
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Export reset function
|
|
141
|
+
parser.resetState = resetParserState;
|
|
142
|
+
%}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
%lex
|
|
146
|
+
|
|
147
|
+
%option flex unicode case-insensitive
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
%%
|
|
151
|
+
|
|
152
|
+
[ \t]+ {}
|
|
153
|
+
(\r?\n)+ return 'NEWLINE'
|
|
154
|
+
\%.* {}
|
|
155
|
+
|
|
156
|
+
\[title return 'HEADER_TITLE'
|
|
157
|
+
\[subtitle return 'HEADER_SUBTITLE'
|
|
158
|
+
\[composer return 'HEADER_COMPOSER'
|
|
159
|
+
\[arranger return 'HEADER_ARRANGER'
|
|
160
|
+
\[lyricist return 'HEADER_LYRICIST'
|
|
161
|
+
\[opus return 'HEADER_OPUS'
|
|
162
|
+
\[instrument return 'HEADER_INSTRUMENT'
|
|
163
|
+
\[genre return 'HEADER_GENRE'
|
|
164
|
+
\] return ']'
|
|
165
|
+
|
|
166
|
+
\"[^"]*\" return 'STRING'
|
|
167
|
+
|
|
168
|
+
"\\clef" return 'CMD_CLEF'
|
|
169
|
+
"\\key" return 'CMD_KEY'
|
|
170
|
+
"\\time" return 'CMD_TIME'
|
|
171
|
+
"\\tempo" return 'CMD_TEMPO'
|
|
172
|
+
"\\staff" return 'CMD_STAFF'
|
|
173
|
+
"\\grace" return 'CMD_GRACE'
|
|
174
|
+
"\\times" return 'CMD_TIMES'
|
|
175
|
+
"\\repeat" return 'CMD_REPEAT'
|
|
176
|
+
"\\ottava" return 'CMD_OTTAVA'
|
|
177
|
+
"\\stemUp" return 'CMD_STEMUP'
|
|
178
|
+
"\\stemDown" return 'CMD_STEMDOWN'
|
|
179
|
+
"\\stemNeutral" return 'CMD_STEMNEUTRAL'
|
|
180
|
+
|
|
181
|
+
"\\major" return 'MODE_MAJOR'
|
|
182
|
+
"\\minor" return 'MODE_MINOR'
|
|
183
|
+
|
|
184
|
+
"\\sustainOn" return 'CMD_SUSTAINON'
|
|
185
|
+
"\\sustainOff" return 'CMD_SUSTAINOFF'
|
|
186
|
+
|
|
187
|
+
"\\bar" return 'CMD_BAR'
|
|
188
|
+
"\\coda" return 'CMD_CODA'
|
|
189
|
+
"\\segno" return 'CMD_SEGNO'
|
|
190
|
+
"\\chords" return 'CMD_CHORDS'
|
|
191
|
+
"\\markup" return 'CMD_MARKUP'
|
|
192
|
+
|
|
193
|
+
"\\<" return 'CMD_CRESC_BEGIN'
|
|
194
|
+
"\\>" return 'CMD_DIM_BEGIN'
|
|
195
|
+
"\\!" return 'CMD_DYNAMICS_END'
|
|
196
|
+
|
|
197
|
+
"\\staccato" return 'ART_STACCATO'
|
|
198
|
+
"\\staccatissimo" return 'ART_STACCATISSIMO'
|
|
199
|
+
"\\tenuto" return 'ART_TENUTO'
|
|
200
|
+
"\\marcato" return 'ART_MARCATO'
|
|
201
|
+
"\\accent" return 'ART_ACCENT'
|
|
202
|
+
"\\portato" return 'ART_PORTATO'
|
|
203
|
+
|
|
204
|
+
"\\trill" return 'ORN_TRILL'
|
|
205
|
+
"\\turn" return 'ORN_TURN'
|
|
206
|
+
"\\mordent" return 'ORN_MORDENT'
|
|
207
|
+
"\\prall" return 'ORN_PRALL'
|
|
208
|
+
"\\fermata" return 'ORN_FERMATA'
|
|
209
|
+
"\\shortfermata" return 'ORN_SHORTFERMATA'
|
|
210
|
+
"\\arpeggio" return 'ORN_ARPEGGIO'
|
|
211
|
+
|
|
212
|
+
"\\ppp" return 'DYN_PPP'
|
|
213
|
+
"\\pp" return 'DYN_PP'
|
|
214
|
+
"\\mp" return 'DYN_MP'
|
|
215
|
+
"\\mf" return 'DYN_MF'
|
|
216
|
+
"\\fff" return 'DYN_FFF'
|
|
217
|
+
"\\ff" return 'DYN_FF'
|
|
218
|
+
"\\sfz" return 'DYN_SFZ'
|
|
219
|
+
"\\rfz" return 'DYN_RFZ'
|
|
220
|
+
"\\sf" return 'DYN_SF'
|
|
221
|
+
"\\p" return 'DYN_P'
|
|
222
|
+
"\\f" return 'DYN_F'
|
|
223
|
+
|
|
224
|
+
"\\rest" return 'CMD_REST'
|
|
225
|
+
|
|
226
|
+
"\\\\\\" return 'PART_SEP'
|
|
227
|
+
"\\\\" return 'VOICE_SEP'
|
|
228
|
+
|
|
229
|
+
"tremolo" return 'TREMOLO'
|
|
230
|
+
|
|
231
|
+
[a-g](ss|ff|s|f)? return 'PITCH'
|
|
232
|
+
|
|
233
|
+
"'" return 'OCT_UP'
|
|
234
|
+
"," return 'OCT_DOWN'
|
|
235
|
+
|
|
236
|
+
[0-9]+ return 'NUMBER'
|
|
237
|
+
|
|
238
|
+
"/" return '/'
|
|
239
|
+
"#" return '#'
|
|
240
|
+
"{" return '{'
|
|
241
|
+
"}" return '}'
|
|
242
|
+
"<" return '<'
|
|
243
|
+
">" return '>'
|
|
244
|
+
"|" return '|'
|
|
245
|
+
"[" return '['
|
|
246
|
+
"]" return ']'
|
|
247
|
+
"(" return '('
|
|
248
|
+
")" return ')'
|
|
249
|
+
"~" return '~'
|
|
250
|
+
"." return '.'
|
|
251
|
+
"-" return '-'
|
|
252
|
+
"_" return '_'
|
|
253
|
+
"^" return '^'
|
|
254
|
+
"!" return '!'
|
|
255
|
+
":" return ':'
|
|
256
|
+
"=" return '='
|
|
257
|
+
|
|
258
|
+
[rR] return 'REST_CHAR'
|
|
259
|
+
[sS] return 'SPACE_CHAR'
|
|
260
|
+
|
|
261
|
+
<<EOF>> return 'EOF'
|
|
262
|
+
|
|
263
|
+
. {}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
/lex
|
|
267
|
+
|
|
268
|
+
%start document
|
|
269
|
+
|
|
270
|
+
%%
|
|
271
|
+
|
|
272
|
+
document
|
|
273
|
+
: content EOF { return { metadata: $1.metadata, measures: $1.measures }; }
|
|
274
|
+
;
|
|
275
|
+
|
|
276
|
+
content
|
|
277
|
+
: headers measures -> ({ metadata: $1, measures: $2 })
|
|
278
|
+
| headers newlines measures -> ({ metadata: $1, measures: $3 })
|
|
279
|
+
| newlines measures -> ({ metadata: undefined, measures: $2 })
|
|
280
|
+
| measures -> ({ metadata: undefined, measures: $1 })
|
|
281
|
+
;
|
|
282
|
+
|
|
283
|
+
newlines
|
|
284
|
+
: NEWLINE
|
|
285
|
+
| newlines NEWLINE
|
|
286
|
+
;
|
|
287
|
+
|
|
288
|
+
headers
|
|
289
|
+
: header -> $1
|
|
290
|
+
| headers header -> ({ ...$1, ...$2 })
|
|
291
|
+
| headers NEWLINE -> $1
|
|
292
|
+
| headers NEWLINE header -> ({ ...$1, ...$3 })
|
|
293
|
+
;
|
|
294
|
+
|
|
295
|
+
header
|
|
296
|
+
: HEADER_TITLE STRING ']' -> ({ title: $2.slice(1, -1) })
|
|
297
|
+
| HEADER_SUBTITLE STRING ']' -> ({ subtitle: $2.slice(1, -1) })
|
|
298
|
+
| HEADER_COMPOSER STRING ']' -> ({ composer: $2.slice(1, -1) })
|
|
299
|
+
| HEADER_ARRANGER STRING ']' -> ({ arranger: $2.slice(1, -1) })
|
|
300
|
+
| HEADER_LYRICIST STRING ']' -> ({ lyricist: $2.slice(1, -1) })
|
|
301
|
+
| HEADER_OPUS STRING ']' -> ({ opus: $2.slice(1, -1) })
|
|
302
|
+
| HEADER_INSTRUMENT STRING ']' -> ({ instrument: $2.slice(1, -1) })
|
|
303
|
+
| HEADER_GENRE STRING ']' -> ({ genre: $2.slice(1, -1) })
|
|
304
|
+
;
|
|
305
|
+
|
|
306
|
+
measures
|
|
307
|
+
: measure_content { $$ = [$1]; }
|
|
308
|
+
| measures '|' measure_content { $$ = $1.concat([$3]); }
|
|
309
|
+
| measures '|' { $$ = $1; }
|
|
310
|
+
;
|
|
311
|
+
|
|
312
|
+
measure_content
|
|
313
|
+
: parts -> measure($1, currentKey, currentTimeSig)
|
|
314
|
+
;
|
|
315
|
+
|
|
316
|
+
parts
|
|
317
|
+
: part_voices { $$ = [part($1)]; }
|
|
318
|
+
| parts PART_SEP part_start part_voices { $$ = $1.concat([part($4)]); }
|
|
319
|
+
;
|
|
320
|
+
|
|
321
|
+
part_start
|
|
322
|
+
: /* empty */ %{ currentStaff = 1; %}
|
|
323
|
+
;
|
|
324
|
+
|
|
325
|
+
part_voices
|
|
326
|
+
: voice_events { $$ = [voice(currentStaff, $1)]; }
|
|
327
|
+
| part_voices VOICE_SEP voice_events { $$ = $1.concat([voice(currentStaff, $3)]); }
|
|
328
|
+
;
|
|
329
|
+
|
|
330
|
+
voice_events
|
|
331
|
+
: /* empty */ { $$ = []; }
|
|
332
|
+
| voice_events event { $$ = $1.concat(Array.isArray($2) ? $2 : [$2]); }
|
|
333
|
+
;
|
|
334
|
+
|
|
335
|
+
event
|
|
336
|
+
: note_event
|
|
337
|
+
| rest_event
|
|
338
|
+
| context_event
|
|
339
|
+
| grace_event
|
|
340
|
+
| tuplet_event
|
|
341
|
+
| tremolo_event
|
|
342
|
+
| pitch_reset_event
|
|
343
|
+
| barline_event
|
|
344
|
+
| harmony_event
|
|
345
|
+
| markup_event
|
|
346
|
+
;
|
|
347
|
+
|
|
348
|
+
barline_event
|
|
349
|
+
: CMD_BAR STRING -> barlineEvent($2.slice(1, -1))
|
|
350
|
+
;
|
|
351
|
+
|
|
352
|
+
harmony_event
|
|
353
|
+
: CMD_CHORDS STRING -> harmonyEvent($2.slice(1, -1))
|
|
354
|
+
;
|
|
355
|
+
|
|
356
|
+
markup_event
|
|
357
|
+
: CMD_MARKUP STRING -> markupEvent($2.slice(1, -1))
|
|
358
|
+
;
|
|
359
|
+
|
|
360
|
+
pitch_reset_event
|
|
361
|
+
: NEWLINE -> ({ type: 'pitchReset' })
|
|
362
|
+
;
|
|
363
|
+
|
|
364
|
+
note_event
|
|
365
|
+
: chord duration post_events %{ currentDuration = $2; $$ = noteEvent($1, $2, $3); %}
|
|
366
|
+
| pitch duration post_events %{ currentDuration = $2; $$ = noteEvent($1, $2, $3); %}
|
|
367
|
+
| chord post_events -> noteEvent($1, currentDuration, $2)
|
|
368
|
+
| pitch post_events -> noteEvent($1, currentDuration, $2)
|
|
369
|
+
;
|
|
370
|
+
|
|
371
|
+
chord
|
|
372
|
+
: '<' pitches '>' -> $2
|
|
373
|
+
;
|
|
374
|
+
|
|
375
|
+
pitches
|
|
376
|
+
: pitch { $$ = [$1]; }
|
|
377
|
+
| pitches pitch { $$ = $1.concat([$2]); }
|
|
378
|
+
;
|
|
379
|
+
|
|
380
|
+
pitch
|
|
381
|
+
: PITCH octave -> parsePitch($1, $2)
|
|
382
|
+
| PITCH -> parsePitch($1, 0)
|
|
383
|
+
;
|
|
384
|
+
|
|
385
|
+
octave
|
|
386
|
+
: OCT_UP -> 1
|
|
387
|
+
| OCT_DOWN -> -1
|
|
388
|
+
| octave OCT_UP -> $1 + 1
|
|
389
|
+
| octave OCT_DOWN -> $1 - 1
|
|
390
|
+
;
|
|
391
|
+
|
|
392
|
+
duration
|
|
393
|
+
: NUMBER dots -> duration(Number($1), $2)
|
|
394
|
+
;
|
|
395
|
+
|
|
396
|
+
dots
|
|
397
|
+
: /* empty */ { $$ = 0; }
|
|
398
|
+
| dots '.' { $$ = $1 + 1; }
|
|
399
|
+
;
|
|
400
|
+
|
|
401
|
+
rest_event
|
|
402
|
+
: REST_CHAR duration post_events %{ currentDuration = $2; $$ = restEvent($2, { fullMeasure: $1 === 'R' }); %}
|
|
403
|
+
| SPACE_CHAR duration post_events %{ currentDuration = $2; $$ = restEvent($2, { invisible: true }); %}
|
|
404
|
+
| REST_CHAR post_events -> restEvent(currentDuration, { fullMeasure: $1 === 'R' })
|
|
405
|
+
| SPACE_CHAR post_events -> restEvent(currentDuration, { invisible: true })
|
|
406
|
+
;
|
|
407
|
+
|
|
408
|
+
context_event
|
|
409
|
+
: clef_cmd -> contextChange({ clef: $1 })
|
|
410
|
+
| key_cmd -> contextChange({ key: $1 })
|
|
411
|
+
| time_cmd -> contextChange({ time: $1 })
|
|
412
|
+
| tempo_cmd -> contextChange({ tempo: $1 })
|
|
413
|
+
| staff_cmd -> contextChange({ staff: $1 })
|
|
414
|
+
| ottava_cmd -> contextChange({ ottava: $1 })
|
|
415
|
+
| stem_cmd -> contextChange({ stemDirection: $1 })
|
|
416
|
+
;
|
|
417
|
+
|
|
418
|
+
clef_cmd
|
|
419
|
+
: CMD_CLEF STRING -> $2.slice(1, -1)
|
|
420
|
+
;
|
|
421
|
+
|
|
422
|
+
key_cmd
|
|
423
|
+
: CMD_KEY pitch_name mode %{ currentKey = keySignature($2.phonet, $2.accidental, $3); $$ = currentKey; %}
|
|
424
|
+
;
|
|
425
|
+
|
|
426
|
+
pitch_name
|
|
427
|
+
: PITCH -> parsePitchName($1)
|
|
428
|
+
;
|
|
429
|
+
|
|
430
|
+
mode
|
|
431
|
+
: MODE_MAJOR -> 'major'
|
|
432
|
+
| MODE_MINOR -> 'minor'
|
|
433
|
+
;
|
|
434
|
+
|
|
435
|
+
time_cmd
|
|
436
|
+
: CMD_TIME NUMBER '/' NUMBER %{ currentTimeSig = fraction(Number($2), Number($4)); $$ = currentTimeSig; %}
|
|
437
|
+
;
|
|
438
|
+
|
|
439
|
+
tempo_cmd
|
|
440
|
+
: CMD_TEMPO STRING duration '=' NUMBER -> ({ text: $2.slice(1, -1), beat: $3, bpm: Number($5) })
|
|
441
|
+
| CMD_TEMPO STRING -> ({ text: $2.slice(1, -1) })
|
|
442
|
+
| CMD_TEMPO duration '=' NUMBER -> ({ beat: $2, bpm: Number($4) })
|
|
443
|
+
;
|
|
444
|
+
|
|
445
|
+
staff_cmd
|
|
446
|
+
: CMD_STAFF STRING %{ currentStaff = Number($2.slice(1, -1)); $$ = currentStaff; %}
|
|
447
|
+
;
|
|
448
|
+
|
|
449
|
+
ottava_cmd
|
|
450
|
+
: CMD_OTTAVA '#' NUMBER -> Number($3)
|
|
451
|
+
| CMD_OTTAVA '#' '-' NUMBER -> -Number($4)
|
|
452
|
+
| CMD_OTTAVA -> 0
|
|
453
|
+
;
|
|
454
|
+
|
|
455
|
+
stem_cmd
|
|
456
|
+
: CMD_STEMUP -> 'up'
|
|
457
|
+
| CMD_STEMDOWN -> 'down'
|
|
458
|
+
| CMD_STEMNEUTRAL -> 'auto'
|
|
459
|
+
;
|
|
460
|
+
|
|
461
|
+
grace_event
|
|
462
|
+
: CMD_GRACE '{' voice_events '}' -> ($3.filter(e => e.type === 'note' || e.type === 'rest').map(e => ({ ...e, grace: true })))
|
|
463
|
+
| CMD_GRACE note_event -> ({ ...$2, grace: true })
|
|
464
|
+
| CMD_GRACE rest_event -> ({ ...$2, grace: true })
|
|
465
|
+
;
|
|
466
|
+
|
|
467
|
+
tuplet_event
|
|
468
|
+
: CMD_TIMES NUMBER '/' NUMBER '{' voice_events '}' -> tupletEvent(fraction(Number($2), Number($4)), $6.filter(e => e.type === 'note' || e.type === 'rest'))
|
|
469
|
+
;
|
|
470
|
+
|
|
471
|
+
tremolo_event
|
|
472
|
+
: CMD_REPEAT TREMOLO NUMBER '{' pitch duration pitch duration '}' %{ currentDuration = $6; $$ = tremoloEvent([$5], [$7], Number($3), $6.division); %}
|
|
473
|
+
| CMD_REPEAT TREMOLO NUMBER '{' pitch duration pitch '}' %{ currentDuration = $6; $$ = tremoloEvent([$5], [$7], Number($3), $6.division); %}
|
|
474
|
+
| CMD_REPEAT TREMOLO NUMBER '{' pitch pitch '}' -> tremoloEvent([$5], [$6], Number($3), currentDuration.division)
|
|
475
|
+
;
|
|
476
|
+
|
|
477
|
+
post_events
|
|
478
|
+
: /* empty */ { $$ = []; }
|
|
479
|
+
| post_events post_event { $$ = $1.concat([$2]); }
|
|
480
|
+
;
|
|
481
|
+
|
|
482
|
+
post_event
|
|
483
|
+
: articulation_mark
|
|
484
|
+
| ornament_mark
|
|
485
|
+
| dynamic_mark
|
|
486
|
+
| hairpin_mark
|
|
487
|
+
| pedal_mark
|
|
488
|
+
| tie_mark
|
|
489
|
+
| slur_mark
|
|
490
|
+
| beam_mark
|
|
491
|
+
| tremolo_mark
|
|
492
|
+
| direction_mark
|
|
493
|
+
| rest_mark
|
|
494
|
+
| fingering_mark
|
|
495
|
+
| navigation_mark
|
|
496
|
+
| markup_mark
|
|
497
|
+
;
|
|
498
|
+
|
|
499
|
+
rest_mark
|
|
500
|
+
: CMD_REST -> ({ pitchedRest: true })
|
|
501
|
+
;
|
|
502
|
+
|
|
503
|
+
articulation_mark
|
|
504
|
+
: ART_STACCATO -> articulation('staccato')
|
|
505
|
+
| ART_STACCATISSIMO -> articulation('staccatissimo')
|
|
506
|
+
| ART_TENUTO -> articulation('tenuto')
|
|
507
|
+
| ART_MARCATO -> articulation('marcato')
|
|
508
|
+
| ART_ACCENT -> articulation('accent')
|
|
509
|
+
| ART_PORTATO -> articulation('portato')
|
|
510
|
+
| '-' '.' -> articulation('staccato')
|
|
511
|
+
| '-' '_' -> articulation('portato')
|
|
512
|
+
| '-' '^' -> articulation('marcato')
|
|
513
|
+
| '-' '>' -> articulation('accent')
|
|
514
|
+
| '-' '!' -> articulation('staccatissimo')
|
|
515
|
+
| '-' '-' -> articulation('tenuto')
|
|
516
|
+
| '>' -> articulation('accent')
|
|
517
|
+
| '.' -> articulation('staccato')
|
|
518
|
+
| '-' -> articulation('tenuto')
|
|
519
|
+
| '!' -> articulation('staccatissimo')
|
|
520
|
+
| '^' -> articulation('marcato')
|
|
521
|
+
| '_' -> articulation('portato')
|
|
522
|
+
;
|
|
523
|
+
|
|
524
|
+
ornament_mark
|
|
525
|
+
: ORN_TRILL -> ornament('trill')
|
|
526
|
+
| ORN_TURN -> ornament('turn')
|
|
527
|
+
| ORN_MORDENT -> ornament('mordent')
|
|
528
|
+
| ORN_PRALL -> ornament('prall')
|
|
529
|
+
| ORN_FERMATA -> ornament('fermata')
|
|
530
|
+
| ORN_SHORTFERMATA -> ornament('shortFermata')
|
|
531
|
+
| ORN_ARPEGGIO -> ornament('arpeggio')
|
|
532
|
+
;
|
|
533
|
+
|
|
534
|
+
dynamic_mark
|
|
535
|
+
: DYN_PPP -> dynamic('ppp')
|
|
536
|
+
| DYN_PP -> dynamic('pp')
|
|
537
|
+
| DYN_P -> dynamic('p')
|
|
538
|
+
| DYN_MP -> dynamic('mp')
|
|
539
|
+
| DYN_MF -> dynamic('mf')
|
|
540
|
+
| DYN_F -> dynamic('f')
|
|
541
|
+
| DYN_FF -> dynamic('ff')
|
|
542
|
+
| DYN_FFF -> dynamic('fff')
|
|
543
|
+
| DYN_SFZ -> dynamic('sfz')
|
|
544
|
+
| DYN_RFZ -> dynamic('rfz')
|
|
545
|
+
| DYN_SF -> dynamic('sfz')
|
|
546
|
+
;
|
|
547
|
+
|
|
548
|
+
hairpin_mark
|
|
549
|
+
: CMD_CRESC_BEGIN -> hairpin('crescendoStart')
|
|
550
|
+
| CMD_DIM_BEGIN -> hairpin('diminuendoStart')
|
|
551
|
+
| CMD_DYNAMICS_END -> hairpin('crescendoEnd')
|
|
552
|
+
;
|
|
553
|
+
|
|
554
|
+
pedal_mark
|
|
555
|
+
: CMD_SUSTAINON -> pedal('sustainOn')
|
|
556
|
+
| CMD_SUSTAINOFF -> pedal('sustainOff')
|
|
557
|
+
;
|
|
558
|
+
|
|
559
|
+
tie_mark
|
|
560
|
+
: '~' -> tie(true)
|
|
561
|
+
;
|
|
562
|
+
|
|
563
|
+
slur_mark
|
|
564
|
+
: '(' -> slur(true)
|
|
565
|
+
| ')' -> slur(false)
|
|
566
|
+
;
|
|
567
|
+
|
|
568
|
+
beam_mark
|
|
569
|
+
: '[' -> beam(true)
|
|
570
|
+
| ']' -> beam(false)
|
|
571
|
+
;
|
|
572
|
+
|
|
573
|
+
tremolo_mark
|
|
574
|
+
: ':' NUMBER -> ({ tremolo: Number($2) })
|
|
575
|
+
;
|
|
576
|
+
|
|
577
|
+
direction_mark
|
|
578
|
+
: '^' post_event -> ({ ...$2, placement: 'above' })
|
|
579
|
+
| '_' post_event -> ({ ...$2, placement: 'below' })
|
|
580
|
+
;
|
|
581
|
+
|
|
582
|
+
fingering_mark
|
|
583
|
+
: '-' NUMBER %{ const n = Number($2); if (n >= 1 && n <= 5) $$ = fingering(n); else $$ = null; %}
|
|
584
|
+
;
|
|
585
|
+
|
|
586
|
+
navigation_mark
|
|
587
|
+
: CMD_CODA -> navigation('coda')
|
|
588
|
+
| CMD_SEGNO -> navigation('segno')
|
|
589
|
+
;
|
|
590
|
+
|
|
591
|
+
markup_mark
|
|
592
|
+
: CMD_MARKUP STRING -> markupMark($2.slice(1, -1))
|
|
593
|
+
;
|