@k-l-lambda/lilylet 0.1.60 → 0.1.63
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/lib/abc/grammar.jison.js +300 -187
- package/lib/lilylet/abcDecoder.js +40 -12
- package/lib/lilylet/grammar.jison.js +273 -169
- package/lib/lilylet/lilypondDecoder.js +163 -39
- package/lib/lilylet/lilypondEncoder.js +120 -7
- package/lib/lilylet/meiEncoder.js +29 -8
- package/lib/lilylet/musicXmlEncoder.js +1 -1
- package/lib/lilylet/parser.d.ts +12 -1
- package/lib/lilylet/parser.js +11 -1
- package/lib/lilylet/serializer.js +33 -4
- package/lib/lilylet/types.d.ts +8 -2
- package/package.json +16 -8
- package/source/abc/TODO.md +97 -0
- package/source/abc/abc.jison +90 -15
- package/source/abc/grammar.jison.js +300 -187
- package/source/lilylet/abcDecoder.ts +42 -14
- package/source/lilylet/grammar.jison.js +273 -169
- package/source/lilylet/lilylet.jison +114 -15
- package/source/lilylet/lilypondDecoder.ts +139 -42
- package/source/lilylet/lilypondEncoder.ts +116 -9
- package/source/lilylet/meiEncoder.ts +32 -9
- package/source/lilylet/musicXmlDecoder.ts +2 -2
- package/source/lilylet/musicXmlEncoder.ts +1 -1
- package/source/lilylet/parser.ts +20 -0
- package/source/lilylet/serializer.ts +31 -6
- package/source/lilylet/types.ts +10 -2
|
@@ -301,6 +301,10 @@ const serializeContextChange = (event) => {
|
|
|
301
301
|
if (event.time) {
|
|
302
302
|
parts.push('\\time ' + event.time.numerator + '/' + event.time.denominator);
|
|
303
303
|
}
|
|
304
|
+
// Partial (pickup measure duration check)
|
|
305
|
+
if (event.partial) {
|
|
306
|
+
parts.push('\\partial ' + event.partial.division + '.'.repeat(event.partial.dots || 0));
|
|
307
|
+
}
|
|
304
308
|
// Ottava
|
|
305
309
|
if (event.ottava !== undefined) {
|
|
306
310
|
if (event.ottava === 0) {
|
|
@@ -343,8 +347,11 @@ const serializeTempo = (tempo) => {
|
|
|
343
347
|
const serializeTupletEvent = (event, env) => {
|
|
344
348
|
const parts = [];
|
|
345
349
|
let currentEnv = env;
|
|
346
|
-
// \times numerator/denominator
|
|
347
|
-
|
|
350
|
+
// \tuplet denominator/numerator { ... } for tuplet type, \times numerator/denominator for times type
|
|
351
|
+
const keyword = event.type === 'times'
|
|
352
|
+
? '\\times ' + event.ratio.numerator + '/' + event.ratio.denominator
|
|
353
|
+
: '\\tuplet ' + event.ratio.denominator + '/' + event.ratio.numerator;
|
|
354
|
+
parts.push(keyword + ' {');
|
|
348
355
|
let prevDuration;
|
|
349
356
|
for (const e of event.events) {
|
|
350
357
|
if (e.type === 'note') {
|
|
@@ -359,6 +366,20 @@ const serializeTupletEvent = (event, env) => {
|
|
|
359
366
|
currentEnv = newEnv;
|
|
360
367
|
prevDuration = e.duration;
|
|
361
368
|
}
|
|
369
|
+
else if (e.type === 'context') {
|
|
370
|
+
const ctx = e;
|
|
371
|
+
if (ctx.staff != null) {
|
|
372
|
+
parts.push(' \\staff "' + ctx.staff + '"');
|
|
373
|
+
}
|
|
374
|
+
else if (ctx.stemDirection != null) {
|
|
375
|
+
if (ctx.stemDirection === StemDirection.up)
|
|
376
|
+
parts.push(' \\stemUp');
|
|
377
|
+
else if (ctx.stemDirection === StemDirection.down)
|
|
378
|
+
parts.push(' \\stemDown');
|
|
379
|
+
else if (ctx.stemDirection === StemDirection.auto)
|
|
380
|
+
parts.push(' \\stemNeutral');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
362
383
|
}
|
|
363
384
|
parts.push(' }');
|
|
364
385
|
return { str: parts.join(''), newEnv: currentEnv };
|
|
@@ -428,6 +449,7 @@ const serializeEvent = (event, env, prevDuration) => {
|
|
|
428
449
|
case 'context':
|
|
429
450
|
return { str: serializeContextChange(event), newEnv: env };
|
|
430
451
|
case 'tuplet':
|
|
452
|
+
case 'times':
|
|
431
453
|
return serializeTupletEvent(event, env);
|
|
432
454
|
case 'tremolo':
|
|
433
455
|
return serializeTremoloEvent(event, env);
|
|
@@ -468,7 +490,7 @@ const serializeVoice = (voice, currentStaff, isGrandStaff = false, measureContex
|
|
|
468
490
|
// before any music collapse to the last one (earlier ones are no-ops).
|
|
469
491
|
// leadStaffScanEnd is the index of the first event that ends this scan —
|
|
470
492
|
// context{staff} events before this index are skipped in the main loop.
|
|
471
|
-
const MUSICAL_TYPES = new Set(['note', 'rest', 'tuplet', 'tremolo']);
|
|
493
|
+
const MUSICAL_TYPES = new Set(['note', 'rest', 'tuplet', 'times', 'tremolo']);
|
|
472
494
|
let effectiveInitialStaff = voice.staff;
|
|
473
495
|
let leadStaffScanEnd = 0;
|
|
474
496
|
for (let i = 0; i < voice.events.length; i++) {
|
|
@@ -607,6 +629,13 @@ const serializeVoice = (voice, currentStaff, isGrandStaff = false, measureContex
|
|
|
607
629
|
else if (event.type === 'rest') {
|
|
608
630
|
prevDuration = event.duration;
|
|
609
631
|
}
|
|
632
|
+
else if (event.type === 'tuplet' || event.type === 'times') {
|
|
633
|
+
// After a tuplet/times block the LilyPond parser's "current duration" is the
|
|
634
|
+
// last note duration inside the tuplet, not the duration before the tuplet.
|
|
635
|
+
// Reset prevDuration so the first note after the block always emits its
|
|
636
|
+
// duration explicitly, avoiding wrong inheritance from inside the tuplet.
|
|
637
|
+
prevDuration = undefined;
|
|
638
|
+
}
|
|
610
639
|
else if (event.type === 'context' && event.clef && emittedClefs) {
|
|
611
640
|
const ctx = event;
|
|
612
641
|
emittedClefs[ctx.staff || activeStaff] = ctx.clef;
|
|
@@ -667,7 +696,7 @@ const serializeMeasure = (measure, _isFirst, currentStaff, isGrandStaff = false,
|
|
|
667
696
|
}
|
|
668
697
|
staff = newStaff;
|
|
669
698
|
}
|
|
670
|
-
parts.push(partStrs.join('
|
|
699
|
+
parts.push(partStrs.join(' \\\\\\\n'));
|
|
671
700
|
}
|
|
672
701
|
return { str: parts.join(' '), newStaff: staff };
|
|
673
702
|
};
|
package/lib/lilylet/types.d.ts
CHANGED
|
@@ -181,6 +181,7 @@ export interface ContextChange {
|
|
|
181
181
|
type: 'context';
|
|
182
182
|
key?: KeySignature;
|
|
183
183
|
time?: Fraction;
|
|
184
|
+
partial?: Duration;
|
|
184
185
|
clef?: Clef;
|
|
185
186
|
ottava?: number;
|
|
186
187
|
stemDirection?: StemDirection;
|
|
@@ -197,7 +198,12 @@ export interface TremoloEvent {
|
|
|
197
198
|
export interface TupletEvent {
|
|
198
199
|
type: 'tuplet';
|
|
199
200
|
ratio: Fraction;
|
|
200
|
-
events: (NoteEvent | RestEvent)[];
|
|
201
|
+
events: (NoteEvent | RestEvent | ContextChange)[];
|
|
202
|
+
}
|
|
203
|
+
export interface TimesEvent {
|
|
204
|
+
type: 'times';
|
|
205
|
+
ratio: Fraction;
|
|
206
|
+
events: (NoteEvent | RestEvent | ContextChange)[];
|
|
201
207
|
}
|
|
202
208
|
export interface PitchResetEvent {
|
|
203
209
|
type: 'pitchReset';
|
|
@@ -215,7 +221,7 @@ export interface MarkupEvent {
|
|
|
215
221
|
content: string;
|
|
216
222
|
placement?: Placement;
|
|
217
223
|
}
|
|
218
|
-
export type Event = NoteEvent | RestEvent | ContextChange | TremoloEvent | TupletEvent | PitchResetEvent | BarlineEvent | HarmonyEvent | MarkupEvent;
|
|
224
|
+
export type Event = NoteEvent | RestEvent | ContextChange | TremoloEvent | TupletEvent | TimesEvent | PitchResetEvent | BarlineEvent | HarmonyEvent | MarkupEvent;
|
|
219
225
|
export interface Voice {
|
|
220
226
|
staff: number;
|
|
221
227
|
events: Event[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k-l-lambda/lilylet",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
4
4
|
"description": "Lilylet is a lilyopnd-like sheet music language designed for Markdown rendering and symbolic music representation in AIGC applications.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -27,12 +27,18 @@
|
|
|
27
27
|
"build": "tsc -p tsconfig.build.json && node tools/convertGrammarToESM.cjs && node tools/fixEsmExtensions.cjs",
|
|
28
28
|
"build:grammar": "npx tsx ./tools/buildJisonParser.ts && node tools/convertGrammarToESM.cjs",
|
|
29
29
|
"prepublishOnly": "npm run build:grammar && npm run build",
|
|
30
|
-
"test": "
|
|
31
|
-
"test:mei": "
|
|
32
|
-
"test:
|
|
33
|
-
"test:
|
|
34
|
-
"test:
|
|
35
|
-
"
|
|
30
|
+
"test": "tsx ./tests/parser.ts",
|
|
31
|
+
"test:mei": "tsx ./tests/mei.ts",
|
|
32
|
+
"test:mei-hashes": "tsx ./tests/computeMeiHashes.ts",
|
|
33
|
+
"test:unit": "tsx ./tests/unit/encodePitch.test.ts",
|
|
34
|
+
"test:partial": "tsx ./tests/unit/partialWarning.test.ts",
|
|
35
|
+
"test:decoder": "tsx ./tests/lilypondDecoder.ts",
|
|
36
|
+
"test:abc": "tsx ./tests/abc-decoder.ts",
|
|
37
|
+
"test:roundtrip": "tsx ./tests/lilypond-roundtrip.ts",
|
|
38
|
+
"test:abc-svg": "tsx ./tests/abc-abcjs-svg.ts",
|
|
39
|
+
"build:tests": "tsc -p tsconfig.tests.json; cp source/lilylet/grammar.jison.js lib-tests/source/lilylet/ && cp source/abc/grammar.jison.js lib-tests/source/abc/ && node tools/fixEsmExtensions.cjs lib-tests && ln -sfn ../../tests/assets lib-tests/tests/assets",
|
|
40
|
+
"test:roundtrip:compiled": "node lib-tests/tests/lilypond-roundtrip.js",
|
|
41
|
+
"ts": "tsx"
|
|
36
42
|
},
|
|
37
43
|
"repository": {
|
|
38
44
|
"type": "git",
|
|
@@ -47,13 +53,15 @@
|
|
|
47
53
|
"devDependencies": {
|
|
48
54
|
"@types/node": "^20.11.20",
|
|
49
55
|
"@types/yargs": "^17.0.32",
|
|
56
|
+
"abcjs": "^6.6.2",
|
|
50
57
|
"formidable": "^3.5.4",
|
|
51
58
|
"jison": "^0.4.18",
|
|
59
|
+
"jsdom": "^29.0.2",
|
|
52
60
|
"sha1": "^1.1.1",
|
|
53
61
|
"ts-node": "^10.9.2",
|
|
62
|
+
"tsx": "^4.21.0",
|
|
54
63
|
"typescript": "^5.3.3",
|
|
55
64
|
"verovio": "^5.7.0",
|
|
56
|
-
"xmldom": "^0.6.0",
|
|
57
65
|
"yargs": "^17.7.2"
|
|
58
66
|
},
|
|
59
67
|
"dependencies": {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# ABC Grammar TODO
|
|
2
|
+
|
|
3
|
+
Issues found by comparing `abc.jison` against the abcjs parser implementation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## High Priority
|
|
8
|
+
|
|
9
|
+
### 1. Church modes in key signatures
|
|
10
|
+
|
|
11
|
+
`key_mode` only distinguishes `major` / `minor`. The `NAME` fallback does a naive
|
|
12
|
+
`startsWith("ma")` check, which mis-classifies all church modes:
|
|
13
|
+
|
|
14
|
+
- Dorian (`Dor`, `dorian`)
|
|
15
|
+
- Phrygian (`Phr`, `phrygian`)
|
|
16
|
+
- Lydian (`Lyd`, `lydian`)
|
|
17
|
+
- Mixolydian (`Mix`, `mixolydian`)
|
|
18
|
+
- Aeolian (`Aeo`, `aeolian`)
|
|
19
|
+
- Locrian (`Loc`, `locrian`)
|
|
20
|
+
- Scottish bagpipe: `HP`, `Hp`
|
|
21
|
+
|
|
22
|
+
These should be recognized as distinct modes rather than silently falling back to
|
|
23
|
+
major or minor. The `abcDecoder.ts` key-mapping logic will also need updating to
|
|
24
|
+
handle these modes.
|
|
25
|
+
|
|
26
|
+
### 2. Bare-number Q: tempo (`Q:120`)
|
|
27
|
+
|
|
28
|
+
`numeric_tempo` only matches `frac '=' number` (e.g. `Q:1/4=120`). Two common
|
|
29
|
+
forms are not parsed:
|
|
30
|
+
|
|
31
|
+
- `Q:120` — plain BPM number (unit inferred from the current meter/L: value)
|
|
32
|
+
- `Q:"Allegro"` — text-only tempo marking
|
|
33
|
+
- `Q:"Allegro" 1/4=120` — combined text + numeric form
|
|
34
|
+
|
|
35
|
+
`Q:120` is the most frequently seen form in real-world ABC files.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Medium Priority
|
|
40
|
+
|
|
41
|
+
### 3. Missing rest type: `y` (spacer)
|
|
42
|
+
|
|
43
|
+
The `rest_phonet` rule covers `z`, `Z`, `x` but not `y` (an invisible spacer rest
|
|
44
|
+
used for spacing/layout). Files containing `y` currently cause a parse error.
|
|
45
|
+
|
|
46
|
+
### 4. Volta ending bracket (`endEnding`)
|
|
47
|
+
|
|
48
|
+
The `bar` rule appends the ending number directly to the bar token string
|
|
49
|
+
(`'|' + N → "|1"`). There is no representation of the *closing* bracket of a
|
|
50
|
+
first-ending, so `[1 ... [2` style repeat structures cannot be round-tripped.
|
|
51
|
+
abcjs tracks `startEnding` / `endEnding` flags on bar elements; consider a
|
|
52
|
+
similar approach.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Low Priority
|
|
57
|
+
|
|
58
|
+
### 5. `~` maps to `mordent` instead of `irishroll`
|
|
59
|
+
|
|
60
|
+
`abc.jison` line 507: `'~' → articulation("mordent")`.
|
|
61
|
+
The standard ABC specification defines `~` as an *Irish roll* ornament, not a
|
|
62
|
+
mordent. abcjs uses the name `irishroll`. The mordent is correctly represented by
|
|
63
|
+
`M`. This is a semantic mismatch that may affect downstream rendering/export.
|
|
64
|
+
|
|
65
|
+
### 6. Microtonal accidentals (`^/`, `_/`)
|
|
66
|
+
|
|
67
|
+
The `accidentals` rule does not cover quarter-tone accidentals:
|
|
68
|
+
|
|
69
|
+
- `^/` → quarter sharp
|
|
70
|
+
- `_/` → quarter flat
|
|
71
|
+
|
|
72
|
+
These are recognised by abcjs. Files using microtonal notation will silently drop
|
|
73
|
+
the accidental.
|
|
74
|
+
|
|
75
|
+
### 7. Short trill decoration `t`
|
|
76
|
+
|
|
77
|
+
The single-letter `t` (half/short trill, `trillh` in abcjs) is not handled. The
|
|
78
|
+
current `P`/`PP` lexer patterns only match uppercase ornament letters
|
|
79
|
+
(`HJLMOPRSTuv`), so lowercase `t` falls through as an unknown token.
|
|
80
|
+
|
|
81
|
+
### 8. Overlay voices (`&`)
|
|
82
|
+
|
|
83
|
+
The `&` operator (alternative voice within a single bar, same staff) is not
|
|
84
|
+
supported. abcjs resolves overlays into separate voices via `resolveOverlays()`.
|
|
85
|
+
This is a moderately common pattern in two-voice piano or lute transcriptions.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Out of Scope (noted for awareness)
|
|
90
|
+
|
|
91
|
+
- **Lyrics** (`w:` / `W:` fields): not handled at the ABC grammar level; notes
|
|
92
|
+
have no `lyric` property. Would require grammar additions and decoder support.
|
|
93
|
+
- **Extended clef types**: only `treble`, `bass`, `tenor` are recognised. abcjs
|
|
94
|
+
also handles `alto`, `baritone`, `mezzo`, `soprano`, `tab`, `perc`, etc.
|
|
95
|
+
- **Decoration name aliases**: abcjs normalises `tr`→`trill`, `emphasis`→`accent`,
|
|
96
|
+
`marcato`→`umarcato`, `<`/`>`→`accent`. The lilylet grammar passes `NAME`
|
|
97
|
+
through as-is; the decoder would need to handle the aliases explicitly.
|
package/source/abc/abc.jison
CHANGED
|
@@ -82,18 +82,24 @@
|
|
|
82
82
|
const {patches} = body;
|
|
83
83
|
const measures = [];
|
|
84
84
|
let measure = null;
|
|
85
|
-
|
|
85
|
+
const seenVoices = new Set();
|
|
86
|
+
|
|
86
87
|
patches.forEach(patch => {
|
|
87
88
|
const voice = patch.control.V || 1;
|
|
88
|
-
if (voice
|
|
89
|
+
if (seenVoices.has(voice)) {
|
|
89
90
|
if (measure)
|
|
90
91
|
measures.push(measure);
|
|
91
92
|
measure = {voices: []};
|
|
93
|
+
seenVoices.clear();
|
|
92
94
|
}
|
|
95
|
+
if (!measure)
|
|
96
|
+
measure = {voices: []};
|
|
97
|
+
seenVoices.add(voice);
|
|
93
98
|
measure.voices.push(patch);
|
|
94
99
|
});
|
|
95
100
|
|
|
96
|
-
|
|
101
|
+
if (measure)
|
|
102
|
+
measures.push(measure);
|
|
97
103
|
|
|
98
104
|
measures.forEach((measure, index) => measure.index = index + 1);
|
|
99
105
|
|
|
@@ -151,6 +157,21 @@
|
|
|
151
157
|
|
|
152
158
|
|
|
153
159
|
const octaveShift = shift => ({octaveShift: shift});
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
const parseMode = (name) => {
|
|
163
|
+
const n = name.toLowerCase();
|
|
164
|
+
if (n.startsWith("ma")) return "major";
|
|
165
|
+
if (n === "m" || n.startsWith("mi")) return "minor";
|
|
166
|
+
if (n.startsWith("dor")) return "dorian";
|
|
167
|
+
if (n.startsWith("phr")) return "phrygian";
|
|
168
|
+
if (n.startsWith("lyd")) return "lydian";
|
|
169
|
+
if (n.startsWith("mix")) return "mixolydian";
|
|
170
|
+
if (n.startsWith("aeo")) return "aeolian";
|
|
171
|
+
if (n.startsWith("loc")) return "locrian";
|
|
172
|
+
if (n === "hp") return "highland";
|
|
173
|
+
return n;
|
|
174
|
+
};
|
|
154
175
|
%}
|
|
155
176
|
|
|
156
177
|
|
|
@@ -160,24 +181,27 @@
|
|
|
160
181
|
|
|
161
182
|
%x string
|
|
162
183
|
%x comment
|
|
184
|
+
%x spec_comment_name
|
|
163
185
|
%x spec_comment
|
|
186
|
+
%x spec_comment_skip
|
|
164
187
|
%x title_string
|
|
165
188
|
%x key_signature
|
|
189
|
+
%x voice_header
|
|
166
190
|
%x exclamation_exp
|
|
167
191
|
|
|
168
|
-
|
|
169
192
|
H \b[A-Z](?=\:[^|])
|
|
170
|
-
A \b[A-G](?=[\W\d\sA-Ga-
|
|
193
|
+
A \b[A-G](?=[\W\d\sA-Ga-g_yzHJLMOPRSTuv]*\b)
|
|
171
194
|
Am \b[A-G](?=[m][a][j]|[m][i][n]\b)
|
|
172
|
-
a \b[a-g](?=[\W\d\sA-Ga-
|
|
195
|
+
a \b[a-g](?=[\W\d\sA-Ga-g_yzHJLMOPRSTuv]*\b)
|
|
173
196
|
z \b[z]
|
|
174
197
|
Z \b[Z]
|
|
175
198
|
x \b[x](?=[\W\d\s])
|
|
199
|
+
y \b[y]
|
|
176
200
|
N [0-9]
|
|
177
201
|
P \b[HJLMOPRSTuv](?=[A-Ga-g][A-Ga-g0-9]*\b)
|
|
178
202
|
PP \b[HJLMOPRSTuv](?=[xz!\[^_=\s"])
|
|
179
203
|
|
|
180
|
-
SPECIAL [:!^_,'/<>={}()\[\]
|
|
204
|
+
SPECIAL [:!^_,'/<>={}()\[\]|.\-+~&]
|
|
181
205
|
|
|
182
206
|
|
|
183
207
|
%%
|
|
@@ -193,10 +217,30 @@ SPECIAL [:!^_,'/<>={}()\[\]|.\-+~]
|
|
|
193
217
|
<title_string>[^\n]+ return 'STR_CONTENT'
|
|
194
218
|
|
|
195
219
|
^[K][:][\s]* { this.pushState('key_signature'); return 'K:'; }
|
|
220
|
+
^[V][:][ \t]* { this.pushState('voice_header'); return 'V:'; }
|
|
221
|
+
<voice_header>\" { this.pushState('string'); return 'STR_START'; }
|
|
222
|
+
<voice_header>[a-zA-Z][a-zA-Z0-9,]* return 'NAME';
|
|
223
|
+
<voice_header>[0-9]+ return 'N';
|
|
224
|
+
<voice_header>[=] return '=';
|
|
225
|
+
<voice_header>[+\-] return yytext;
|
|
226
|
+
<voice_header>[ \t]+ {}
|
|
227
|
+
<voice_header>\n { this.popState(); }
|
|
228
|
+
<voice_header>\] { this.popState(); return ']'; }
|
|
196
229
|
<key_signature>"treble" return 'TREBLE';
|
|
197
230
|
<key_signature>"bass" return 'BASS';
|
|
198
231
|
<key_signature>"tenor" return 'TENOR';
|
|
232
|
+
<key_signature>"none" return 'NAME';
|
|
233
|
+
<key_signature>"Dor" return 'NAME';
|
|
234
|
+
<key_signature>"Phr" return 'NAME';
|
|
235
|
+
<key_signature>"Lyd" return 'NAME';
|
|
236
|
+
<key_signature>"Mix" return 'NAME';
|
|
237
|
+
<key_signature>"Aeo" return 'NAME';
|
|
238
|
+
<key_signature>"Loc" return 'NAME';
|
|
239
|
+
<key_signature>"HP" return 'NAME';
|
|
240
|
+
<key_signature>"Hp" return 'NAME';
|
|
241
|
+
<key_signature>[a-z]+[ \t]*=[^\n\]]* {}
|
|
199
242
|
<key_signature>[A-G] return 'A';
|
|
243
|
+
<key_signature>[A-Z][a-z]+ return 'NAME';
|
|
200
244
|
<key_signature>[b] return 'FLAT';
|
|
201
245
|
<key_signature>[#] return 'SHARP';
|
|
202
246
|
<key_signature>[m][a-z]* return 'NAME';
|
|
@@ -208,14 +252,20 @@ SPECIAL [:!^_,'/<>={}()\[\]|.\-+~]
|
|
|
208
252
|
<key_signature>\] { this.popState(); return ']'; }
|
|
209
253
|
|
|
210
254
|
^[%] { this.pushState('comment'); }
|
|
211
|
-
<comment>[%] { this.pushState('
|
|
255
|
+
<comment>[%] { this.pushState('spec_comment_name'); }
|
|
212
256
|
<comment>[^\n]+ { return 'COMMENT'; }
|
|
213
|
-
<spec_comment>\n { this.popState(); this.popState(); }
|
|
214
257
|
<comment>\n { this.popState(); }
|
|
215
|
-
<
|
|
216
|
-
<
|
|
217
|
-
<spec_comment
|
|
258
|
+
<spec_comment_name>[ \t]+ {}
|
|
259
|
+
<spec_comment_name>"score" { this.popState(); this.pushState('spec_comment'); return 'SCORE'; }
|
|
260
|
+
<spec_comment_name>"staves" { this.popState(); this.pushState('spec_comment'); return 'SCORE'; }
|
|
261
|
+
<spec_comment_name>[\w]+ { this.popState(); this.pushState('spec_comment_skip'); }
|
|
262
|
+
<spec_comment_name>\n { this.popState(); this.popState(); }
|
|
263
|
+
<spec_comment>[ \t]+ {}
|
|
218
264
|
<spec_comment>[(){}\[\]|] return yytext
|
|
265
|
+
<spec_comment>[\w]+ return 'NN'
|
|
266
|
+
<spec_comment>\n { this.popState(); this.popState(); return 'LAYOUT_END'; }
|
|
267
|
+
<spec_comment_skip>[^\n]+ {}
|
|
268
|
+
<spec_comment_skip>\n { this.popState(); this.popState(); }
|
|
219
269
|
|
|
220
270
|
[!] { this.pushState('exclamation_exp'); return '!'; }
|
|
221
271
|
<exclamation_exp>[!] { this.popState(); return '!'; }
|
|
@@ -231,6 +281,7 @@ SPECIAL [:!^_,'/<>={}()\[\]|.\-+~]
|
|
|
231
281
|
<exclamation_exp>{N} return 'N'
|
|
232
282
|
<exclamation_exp>[a-zA-Z][\w-]* return 'NAME'
|
|
233
283
|
|
|
284
|
+
\\\n {}
|
|
234
285
|
\s+ {}
|
|
235
286
|
|
|
236
287
|
{SPECIAL} return yytext
|
|
@@ -250,6 +301,7 @@ SPECIAL [:!^_,'/<>={}()\[\]|.\-+~]
|
|
|
250
301
|
"staff" return 'STAFF'
|
|
251
302
|
"maj" return 'MAJ'
|
|
252
303
|
"min" return 'MIN'
|
|
304
|
+
{y} return 'y'
|
|
253
305
|
[a-zA-Z][\w-]* return 'NAME'
|
|
254
306
|
|
|
255
307
|
<<EOF>> return 'EOF'
|
|
@@ -284,6 +336,7 @@ head_lines
|
|
|
284
336
|
| head_lines head_line -> [...$1, $2]
|
|
285
337
|
| head_lines comment -> [...$1, $2]
|
|
286
338
|
| head_lines staff_layout_statement -> [...$1, $2]
|
|
339
|
+
| head_lines 'LAYOUT_END' -> $1
|
|
287
340
|
| head_lines ']' -> $1
|
|
288
341
|
| head_lines '}' -> $1
|
|
289
342
|
| head_lines ')' -> $1
|
|
@@ -294,7 +347,7 @@ comment
|
|
|
294
347
|
;
|
|
295
348
|
|
|
296
349
|
staff_layout_statement
|
|
297
|
-
: 'SCORE' staff_layout
|
|
350
|
+
: 'SCORE' staff_layout 'LAYOUT_END' -> $2
|
|
298
351
|
;
|
|
299
352
|
|
|
300
353
|
staff_layout
|
|
@@ -320,6 +373,7 @@ head_line
|
|
|
320
373
|
: 'T:' string_content -> header('T', $2)
|
|
321
374
|
| 'C:' string_content -> header('C', $2)
|
|
322
375
|
| 'K:' key_signature -> header('K', $2)
|
|
376
|
+
| 'V:' header_value -> header('V', $2)
|
|
323
377
|
| H ':' header_value -> header($1, $3)
|
|
324
378
|
;
|
|
325
379
|
|
|
@@ -328,6 +382,7 @@ header_value
|
|
|
328
382
|
| number
|
|
329
383
|
| frac
|
|
330
384
|
| numeric_tempo
|
|
385
|
+
| string frac '=' number -> ({text: $1, note: $2, bpm: $4})
|
|
331
386
|
| upper_phonet
|
|
332
387
|
| voice_exp
|
|
333
388
|
| staff_shift
|
|
@@ -342,6 +397,11 @@ staff_shift
|
|
|
342
397
|
;
|
|
343
398
|
|
|
344
399
|
key_signature
|
|
400
|
+
: key_root -> $1
|
|
401
|
+
| NAME -> key(null, $1)
|
|
402
|
+
;
|
|
403
|
+
|
|
404
|
+
key_root
|
|
345
405
|
: A -> key($1, null)
|
|
346
406
|
| A sharp_or_flat -> key($1 + $2, null)
|
|
347
407
|
| A key_mode -> key($1, $2)
|
|
@@ -362,7 +422,7 @@ sharp_or_flat
|
|
|
362
422
|
key_mode
|
|
363
423
|
: MAJ -> 'major'
|
|
364
424
|
| MIN -> 'minor'
|
|
365
|
-
| NAME -> $1
|
|
425
|
+
| NAME -> parseMode($1)
|
|
366
426
|
;
|
|
367
427
|
|
|
368
428
|
plus_minus_number
|
|
@@ -402,6 +462,10 @@ voice_exp
|
|
|
402
462
|
| number NAME assigns -> voice($1, $2, $3)
|
|
403
463
|
| NAME -> voice(1, $1)
|
|
404
464
|
| NAME assigns -> voice(1, $1, $2)
|
|
465
|
+
| upper_phonet number -> voice(1, $1 + String($2))
|
|
466
|
+
| upper_phonet number assigns -> voice(1, $1 + String($2), $3)
|
|
467
|
+
| upper_phonet number NAME -> voice(1, $1 + String($2))
|
|
468
|
+
| upper_phonet number NAME assigns -> voice(1, $1 + String($2), $4)
|
|
405
469
|
;
|
|
406
470
|
|
|
407
471
|
assigns
|
|
@@ -418,6 +482,7 @@ assign_value
|
|
|
418
482
|
| number
|
|
419
483
|
| plus_minus_number
|
|
420
484
|
| NAME
|
|
485
|
+
| upper_phonet
|
|
421
486
|
;
|
|
422
487
|
|
|
423
488
|
upper_phonet
|
|
@@ -437,6 +502,9 @@ patches
|
|
|
437
502
|
| patches tailless_patch -> [...$1, $2]
|
|
438
503
|
| patches ']' -> $1
|
|
439
504
|
| patches '}' -> $1
|
|
505
|
+
| patches head_line -> $1
|
|
506
|
+
| patches 'LAYOUT_END' -> $1
|
|
507
|
+
| patches '&' patch -> $1
|
|
440
508
|
;
|
|
441
509
|
|
|
442
510
|
patch
|
|
@@ -459,10 +527,11 @@ bar
|
|
|
459
527
|
| ':' '|' ']' -> ':|]'
|
|
460
528
|
| '|' N -> '|' + $2
|
|
461
529
|
| ':' '|' N -> ':|' + $2
|
|
530
|
+
| '&' -> '&'
|
|
462
531
|
;
|
|
463
532
|
|
|
464
533
|
music
|
|
465
|
-
: %empty
|
|
534
|
+
: %empty -> []
|
|
466
535
|
| music expressive_mark -> $1 ? [...$1, $2] : [$2]
|
|
467
536
|
| music text -> $1 ? [...$1, $2] : [$2]
|
|
468
537
|
| music event -> $1 ? [...$1, $2] : [$2]
|
|
@@ -474,11 +543,13 @@ music
|
|
|
474
543
|
| music NAME -> $1
|
|
475
544
|
| music '^' NAME -> $1
|
|
476
545
|
| music '^' -> $1
|
|
546
|
+
| music '[' N -> $1
|
|
477
547
|
;
|
|
478
548
|
|
|
479
549
|
control
|
|
480
550
|
: '[' H ':' header_value ']' -> ({control: header($2, $4)})
|
|
481
551
|
| '[' 'K:' header_value ']' -> ({control: header("K", $3)})
|
|
552
|
+
| '[' 'V:' header_value ']' -> ({control: header("V", $3)})
|
|
482
553
|
| '[' NAME ':' header_value ']' -> ({control: header($2, $4)})
|
|
483
554
|
;
|
|
484
555
|
|
|
@@ -510,6 +581,7 @@ articulation
|
|
|
510
581
|
articulation_content
|
|
511
582
|
: scope_articulation -> articulation($1)
|
|
512
583
|
| scope_articulation parenthese -> articulation($1, $2)
|
|
584
|
+
| scope_articulation '=' assign_value -> articulation($1 + '=' + String($3))
|
|
513
585
|
| DYNAMIC -> articulation($1)
|
|
514
586
|
| a -> articulation($1)
|
|
515
587
|
| "^" -> articulation($1)
|
|
@@ -613,6 +685,8 @@ accidentals
|
|
|
613
685
|
| '^' '^' -> 2
|
|
614
686
|
| '_' '_' -> -2
|
|
615
687
|
| '=' '=' -> 0
|
|
688
|
+
| '^' '/' -> 0.5
|
|
689
|
+
| '_' '/' -> -0.5
|
|
616
690
|
;
|
|
617
691
|
|
|
618
692
|
pitch
|
|
@@ -637,6 +711,7 @@ rest_phonet
|
|
|
637
711
|
: z
|
|
638
712
|
| Z
|
|
639
713
|
| x
|
|
714
|
+
| y
|
|
640
715
|
;
|
|
641
716
|
|
|
642
717
|
event
|