@k-l-lambda/lilylet 0.1.34 → 0.1.35
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/grammar.jison.js +179 -150
- package/lib/meiEncoder.js +21 -3
- package/lib/serializer.js +7 -1
- package/lib/types.d.ts +4 -1
- package/package.json +1 -1
- package/source/lilylet/grammar.jison.js +179 -150
- package/source/lilylet/lilylet.jison +30 -2
- package/source/lilylet/meiEncoder.ts +22 -3
- package/source/lilylet/serializer.ts +8 -2
- package/source/lilylet/types.ts +7 -1
- package/lib/lilypondDecoder.d.ts +0 -28
- package/lib/lilypondDecoder.js +0 -645
|
@@ -128,6 +128,7 @@
|
|
|
128
128
|
let currentKey = null;
|
|
129
129
|
let currentTimeSig = null;
|
|
130
130
|
let currentDuration = { division: 4, dots: 0 }; // default quarter note
|
|
131
|
+
let numericTimeSignature = false; // When true, 4/4 and 2/2 use numeric display instead of C/C|
|
|
131
132
|
|
|
132
133
|
// Reset parser state - call before each parse
|
|
133
134
|
const resetParserState = () => {
|
|
@@ -135,6 +136,7 @@
|
|
|
135
136
|
currentKey = null;
|
|
136
137
|
currentTimeSig = null;
|
|
137
138
|
currentDuration = { division: 4, dots: 0 };
|
|
139
|
+
numericTimeSignature = false;
|
|
138
140
|
};
|
|
139
141
|
|
|
140
142
|
// Export reset function
|
|
@@ -168,6 +170,8 @@
|
|
|
168
170
|
"\\clef" return 'CMD_CLEF'
|
|
169
171
|
"\\key" return 'CMD_KEY'
|
|
170
172
|
"\\time" return 'CMD_TIME'
|
|
173
|
+
"\\numericTimeSignature" return 'CMD_NUMERIC_TIME_SIG'
|
|
174
|
+
"\\defaultTimeSignature" return 'CMD_DEFAULT_TIME_SIG'
|
|
171
175
|
"\\tempo" return 'CMD_TEMPO'
|
|
172
176
|
"\\staff" return 'CMD_STAFF'
|
|
173
177
|
"\\grace" return 'CMD_GRACE'
|
|
@@ -329,7 +333,7 @@ part_voices
|
|
|
329
333
|
|
|
330
334
|
voice_events
|
|
331
335
|
: /* empty */ { $$ = []; }
|
|
332
|
-
| voice_events event { $$ = $1.concat(Array.isArray($2) ? $2 : [$2]); }
|
|
336
|
+
| voice_events event { $$ = $2 === null ? $1 : $1.concat(Array.isArray($2) ? $2 : [$2]); }
|
|
333
337
|
;
|
|
334
338
|
|
|
335
339
|
event
|
|
@@ -413,6 +417,8 @@ context_event
|
|
|
413
417
|
| staff_cmd -> contextChange({ staff: $1 })
|
|
414
418
|
| ottava_cmd -> contextChange({ ottava: $1 })
|
|
415
419
|
| stem_cmd -> contextChange({ stemDirection: $1 })
|
|
420
|
+
| numeric_time_sig_cmd -> null
|
|
421
|
+
| default_time_sig_cmd -> null
|
|
416
422
|
;
|
|
417
423
|
|
|
418
424
|
clef_cmd
|
|
@@ -433,7 +439,29 @@ mode
|
|
|
433
439
|
;
|
|
434
440
|
|
|
435
441
|
time_cmd
|
|
436
|
-
: CMD_TIME NUMBER '/' NUMBER %{
|
|
442
|
+
: CMD_TIME NUMBER '/' NUMBER %{
|
|
443
|
+
const num = Number($2);
|
|
444
|
+
const den = Number($4);
|
|
445
|
+
const timeSig = fraction(num, den);
|
|
446
|
+
// Add symbol for 4/4 (common) and 2/2 (cut) unless numericTimeSignature is set
|
|
447
|
+
if (!numericTimeSignature) {
|
|
448
|
+
if (num === 4 && den === 4) {
|
|
449
|
+
timeSig.symbol = 'common';
|
|
450
|
+
} else if (num === 2 && den === 2) {
|
|
451
|
+
timeSig.symbol = 'cut';
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
currentTimeSig = timeSig;
|
|
455
|
+
$$ = currentTimeSig;
|
|
456
|
+
%}
|
|
457
|
+
;
|
|
458
|
+
|
|
459
|
+
numeric_time_sig_cmd
|
|
460
|
+
: CMD_NUMERIC_TIME_SIG %{ numericTimeSignature = true; $$ = null; %}
|
|
461
|
+
;
|
|
462
|
+
|
|
463
|
+
default_time_sig_cmd
|
|
464
|
+
: CMD_DEFAULT_TIME_SIG %{ numericTimeSignature = false; $$ = null; %}
|
|
437
465
|
;
|
|
438
466
|
|
|
439
467
|
tempo_cmd
|
|
@@ -1586,11 +1586,14 @@ const encodeScoreDef = (
|
|
|
1586
1586
|
timeNum: number,
|
|
1587
1587
|
timeDen: number,
|
|
1588
1588
|
partInfos: PartInfo[],
|
|
1589
|
-
indent: string
|
|
1589
|
+
indent: string,
|
|
1590
|
+
meterSymbol?: 'common' | 'cut'
|
|
1590
1591
|
): string => {
|
|
1591
1592
|
const scoreDefId = generateId("scoredef");
|
|
1592
1593
|
|
|
1593
|
-
|
|
1594
|
+
// Build meter attributes
|
|
1595
|
+
const meterSymAttr = meterSymbol ? ` meter.sym="${meterSymbol}"` : '';
|
|
1596
|
+
let xml = `${indent}<scoreDef xml:id="${scoreDefId}" key.sig="${keySig}"${meterSymAttr} meter.count="${timeNum}" meter.unit="${timeDen}">\n`;
|
|
1594
1597
|
xml += `${indent} <staffGrp xml:id="${generateId("staffgrp")}">\n`;
|
|
1595
1598
|
|
|
1596
1599
|
for (let pi = 0; pi < partInfos.length; pi++) {
|
|
@@ -1640,6 +1643,7 @@ const encode = (doc: LilyletDoc, options: MEIEncoderOptions = {}): string => {
|
|
|
1640
1643
|
let currentKey = 0;
|
|
1641
1644
|
let currentTimeNum = 4;
|
|
1642
1645
|
let currentTimeDen = 4;
|
|
1646
|
+
let currentMeterSymbol: 'common' | 'cut' | undefined = undefined;
|
|
1643
1647
|
|
|
1644
1648
|
const firstMeasure = doc.measures[0];
|
|
1645
1649
|
if (firstMeasure.key) {
|
|
@@ -1648,6 +1652,7 @@ const encode = (doc: LilyletDoc, options: MEIEncoderOptions = {}): string => {
|
|
|
1648
1652
|
if (firstMeasure.timeSig) {
|
|
1649
1653
|
currentTimeNum = firstMeasure.timeSig.numerator;
|
|
1650
1654
|
currentTimeDen = firstMeasure.timeSig.denominator;
|
|
1655
|
+
currentMeterSymbol = firstMeasure.timeSig.symbol;
|
|
1651
1656
|
}
|
|
1652
1657
|
|
|
1653
1658
|
const keySig = KEY_SIGS[currentKey] || "0";
|
|
@@ -1701,7 +1706,7 @@ const encode = (doc: LilyletDoc, options: MEIEncoderOptions = {}): string => {
|
|
|
1701
1706
|
mei += `${indent}${indent}<body>\n`;
|
|
1702
1707
|
mei += `${indent}${indent}${indent}<mdiv xml:id="${generateId("mdiv")}">\n`;
|
|
1703
1708
|
mei += `${indent}${indent}${indent}${indent}<score xml:id="${generateId("score")}">\n`;
|
|
1704
|
-
mei += encodeScoreDef(keySig, currentTimeNum, currentTimeDen, partInfos, `${indent}${indent}${indent}${indent}${indent}
|
|
1709
|
+
mei += encodeScoreDef(keySig, currentTimeNum, currentTimeDen, partInfos, `${indent}${indent}${indent}${indent}${indent}`, currentMeterSymbol);
|
|
1705
1710
|
mei += `${indent}${indent}${indent}${indent}${indent}<section xml:id="${generateId("section")}">\n`;
|
|
1706
1711
|
|
|
1707
1712
|
// Track tie state across measures for cross-measure ties
|
|
@@ -1757,6 +1762,20 @@ const encode = (doc: LilyletDoc, options: MEIEncoderOptions = {}): string => {
|
|
|
1757
1762
|
mei += `${indent}${indent}${indent}${indent}${indent}${indent}<scoreDef xml:id="${generateId('scoredef')}" key.sig="${newKeySig}" />\n`;
|
|
1758
1763
|
}
|
|
1759
1764
|
}
|
|
1765
|
+
// Check for time signature change and output scoreDef if needed
|
|
1766
|
+
if (measure.timeSig && mi > 0) {
|
|
1767
|
+
const newTimeNum = measure.timeSig.numerator;
|
|
1768
|
+
const newTimeDen = measure.timeSig.denominator;
|
|
1769
|
+
const newMeterSymbol = measure.timeSig.symbol;
|
|
1770
|
+
if (newTimeNum !== currentTimeNum || newTimeDen !== currentTimeDen || newMeterSymbol !== currentMeterSymbol) {
|
|
1771
|
+
currentTimeNum = newTimeNum;
|
|
1772
|
+
currentTimeDen = newTimeDen;
|
|
1773
|
+
currentMeterSymbol = newMeterSymbol;
|
|
1774
|
+
// Output a scoreDef with the new time signature
|
|
1775
|
+
const meterSymAttr = currentMeterSymbol ? ` meter.sym="${currentMeterSymbol}"` : '';
|
|
1776
|
+
mei += `${indent}${indent}${indent}${indent}${indent}${indent}<scoreDef xml:id="${generateId('scoredef')}"${meterSymAttr} meter.count="${currentTimeNum}" meter.unit="${currentTimeDen}" />\n`;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1760
1779
|
mei += encodeMeasure(measure, mi + 1, `${indent}${indent}${indent}${indent}${indent}${indent}`, totalStaves, tieState, slurState, hairpinState, currentKey, partInfos, clefState);
|
|
1761
1780
|
});
|
|
1762
1781
|
|
|
@@ -550,7 +550,7 @@ const serializeEvent = (
|
|
|
550
550
|
// Key/time signature info to inject into first voice
|
|
551
551
|
interface MeasureContext {
|
|
552
552
|
key?: KeySignature;
|
|
553
|
-
time?: { numerator: number; denominator: number };
|
|
553
|
+
time?: { numerator: number; denominator: number; symbol?: 'common' | 'cut' };
|
|
554
554
|
}
|
|
555
555
|
|
|
556
556
|
// Serialize a voice with pitch environment tracking
|
|
@@ -585,7 +585,13 @@ const serializeVoice = (
|
|
|
585
585
|
parts.push('\\key ' + keyStr);
|
|
586
586
|
}
|
|
587
587
|
if (measureContext.time) {
|
|
588
|
-
|
|
588
|
+
const { numerator, denominator, symbol } = measureContext.time;
|
|
589
|
+
// Output \numericTimeSignature before 4/4 or 2/2 if no symbol is set
|
|
590
|
+
// (meaning numeric display was explicitly requested)
|
|
591
|
+
if (!symbol && ((numerator === 4 && denominator === 4) || (numerator === 2 && denominator === 2))) {
|
|
592
|
+
parts.push('\\numericTimeSignature');
|
|
593
|
+
}
|
|
594
|
+
parts.push('\\time ' + numerator + '/' + denominator);
|
|
589
595
|
}
|
|
590
596
|
}
|
|
591
597
|
|
package/source/lilylet/types.ts
CHANGED
|
@@ -99,6 +99,12 @@ export interface Fraction {
|
|
|
99
99
|
denominator: number;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
// Time signature with optional symbol display
|
|
103
|
+
// symbol: 'common' for C (4/4), 'cut' for C| (2/2), undefined for numeric
|
|
104
|
+
export interface TimeSig extends Fraction {
|
|
105
|
+
symbol?: 'common' | 'cut';
|
|
106
|
+
}
|
|
107
|
+
|
|
102
108
|
export interface Pitch {
|
|
103
109
|
phonet: Phonet;
|
|
104
110
|
accidental?: Accidental;
|
|
@@ -292,7 +298,7 @@ export interface Part {
|
|
|
292
298
|
// Measure contains parts separated by \\\
|
|
293
299
|
export interface Measure {
|
|
294
300
|
key?: KeySignature;
|
|
295
|
-
timeSig?:
|
|
301
|
+
timeSig?: TimeSig;
|
|
296
302
|
parts: Part[];
|
|
297
303
|
partial?: boolean;
|
|
298
304
|
}
|
package/lib/lilypondDecoder.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LilyPond to Lilylet Decoder
|
|
3
|
-
*
|
|
4
|
-
* Converts LilyPond notation files to Lilylet document format using the lotus parser.
|
|
5
|
-
* This module is browser-compatible - it uses pre-compiled parser from lotus.
|
|
6
|
-
*/
|
|
7
|
-
import * as lilyParser from "@k-l-lambda/lotus/lib/inc/lilyParser/index.js";
|
|
8
|
-
import { LilyletDoc, Event, Fraction } from "./types";
|
|
9
|
-
interface ParsedMeasure {
|
|
10
|
-
key: number | null;
|
|
11
|
-
timeSig: Fraction | null;
|
|
12
|
-
voices: ParsedVoice[];
|
|
13
|
-
partial: boolean;
|
|
14
|
-
}
|
|
15
|
-
interface ParsedVoice {
|
|
16
|
-
staff: number;
|
|
17
|
-
events: Event[];
|
|
18
|
-
}
|
|
19
|
-
declare const parseLilyDocument: (lilyDocument: lilyParser.LilyDocument) => ParsedMeasure[];
|
|
20
|
-
/**
|
|
21
|
-
* Decode a LilyPond string to LilyletDoc (synchronous, browser-compatible)
|
|
22
|
-
*/
|
|
23
|
-
declare const decode: (lilypondSource: string) => LilyletDoc;
|
|
24
|
-
/**
|
|
25
|
-
* Decode from pre-parsed LilyDocument (synchronous, for when you already have parsed data)
|
|
26
|
-
*/
|
|
27
|
-
declare const decodeFromDocument: (lilyDocument: lilyParser.LilyDocument) => LilyletDoc;
|
|
28
|
-
export { decode, decodeFromDocument, parseLilyDocument, };
|