@k-l-lambda/lilylet 0.1.53 → 0.1.54
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.
|
@@ -329,7 +329,7 @@ case 92:
|
|
|
329
329
|
this.$ = 'auto';
|
|
330
330
|
break;
|
|
331
331
|
case 93:
|
|
332
|
-
this.$ = ($$[$0-1].
|
|
332
|
+
this.$ = ($$[$0-1].map(e => (e.type === 'note' || e.type === 'rest') ? { ...e, grace: true } : e));
|
|
333
333
|
break;
|
|
334
334
|
case 94: case 95:
|
|
335
335
|
this.$ = ({ ...$$[$0], grace: true });
|
|
@@ -754,15 +754,35 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
|
|
|
754
754
|
}
|
|
755
755
|
return true;
|
|
756
756
|
};
|
|
757
|
+
let graceBeamOpen = false; // Whether a grace note <beam> is open (independent of main beam)
|
|
757
758
|
for (const event of voice.events) {
|
|
758
759
|
// Check for beam start/end in this event (including inside tuplets)
|
|
759
760
|
const { beamStart, beamEnd } = getEventBeamMarks(event);
|
|
760
|
-
//
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
761
|
+
// Grace notes have independent beam groups - don't interfere with main beam
|
|
762
|
+
const isGraceNote = event.type === 'note' && event.grace;
|
|
763
|
+
if (isGraceNote) {
|
|
764
|
+
// Grace beam: open/close independently, nested inside parent beam if open
|
|
765
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
766
|
+
if (beamStart && !graceBeamOpen) {
|
|
767
|
+
xml += `${graceBaseIndent}<beam xml:id="${generateId('beam')}">\n`;
|
|
768
|
+
graceBeamOpen = true;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
else {
|
|
772
|
+
// Close any open grace beam before processing a non-grace event
|
|
773
|
+
if (graceBeamOpen) {
|
|
774
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
775
|
+
xml += `${graceBaseIndent}</beam>\n`;
|
|
776
|
+
graceBeamOpen = false;
|
|
777
|
+
}
|
|
778
|
+
// Open main beam element if beam starts
|
|
779
|
+
if (beamStart && !beamElementOpen) {
|
|
780
|
+
xml += `${baseIndent}<beam xml:id="${generateId('beam')}">\n`;
|
|
781
|
+
beamElementOpen = true;
|
|
782
|
+
}
|
|
764
783
|
}
|
|
765
|
-
const
|
|
784
|
+
const graceIndentBase = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
785
|
+
const currentIndent = graceBeamOpen ? graceIndentBase + ' ' : (beamElementOpen ? baseIndent + ' ' : baseIndent);
|
|
766
786
|
switch (event.type) {
|
|
767
787
|
case 'note': {
|
|
768
788
|
const noteEvent = event;
|
|
@@ -1002,11 +1022,25 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
|
|
|
1002
1022
|
break;
|
|
1003
1023
|
}
|
|
1004
1024
|
// Close beam element if beam ends
|
|
1005
|
-
if (beamEnd
|
|
1006
|
-
|
|
1007
|
-
|
|
1025
|
+
if (beamEnd) {
|
|
1026
|
+
if (isGraceNote && graceBeamOpen) {
|
|
1027
|
+
// Close grace beam
|
|
1028
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
1029
|
+
xml += `${graceBaseIndent}</beam>\n`;
|
|
1030
|
+
graceBeamOpen = false;
|
|
1031
|
+
}
|
|
1032
|
+
else if (!isGraceNote && beamElementOpen) {
|
|
1033
|
+
// Close main beam
|
|
1034
|
+
xml += `${baseIndent}</beam>\n`;
|
|
1035
|
+
beamElementOpen = false;
|
|
1036
|
+
}
|
|
1008
1037
|
}
|
|
1009
1038
|
}
|
|
1039
|
+
// Close any unclosed grace beam
|
|
1040
|
+
if (graceBeamOpen) {
|
|
1041
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
1042
|
+
xml += `${graceBaseIndent}</beam>\n`;
|
|
1043
|
+
}
|
|
1010
1044
|
// Close any unclosed beam
|
|
1011
1045
|
if (beamElementOpen) {
|
|
1012
1046
|
xml += `${baseIndent}</beam>\n`;
|
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.54",
|
|
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",
|
|
@@ -329,7 +329,7 @@ case 92:
|
|
|
329
329
|
this.$ = 'auto';
|
|
330
330
|
break;
|
|
331
331
|
case 93:
|
|
332
|
-
this.$ = ($$[$0-1].
|
|
332
|
+
this.$ = ($$[$0-1].map(e => (e.type === 'note' || e.type === 'rest') ? { ...e, grace: true } : e));
|
|
333
333
|
break;
|
|
334
334
|
case 94: case 95:
|
|
335
335
|
this.$ = ({ ...$$[$0], grace: true });
|
|
@@ -501,7 +501,7 @@ stem_cmd
|
|
|
501
501
|
;
|
|
502
502
|
|
|
503
503
|
grace_event
|
|
504
|
-
: CMD_GRACE '{' voice_events '}' -> ($3.
|
|
504
|
+
: CMD_GRACE '{' voice_events '}' -> ($3.map(e => (e.type === 'note' || e.type === 'rest') ? { ...e, grace: true } : e))
|
|
505
505
|
| CMD_GRACE note_event -> ({ ...$2, grace: true })
|
|
506
506
|
| CMD_GRACE rest_event -> ({ ...$2, grace: true })
|
|
507
507
|
;
|
|
@@ -1031,17 +1031,39 @@ const encodeLayer = (voice: Voice, layerN: number, indent: string, initialTiePit
|
|
|
1031
1031
|
return true;
|
|
1032
1032
|
};
|
|
1033
1033
|
|
|
1034
|
+
let graceBeamOpen = false; // Whether a grace note <beam> is open (independent of main beam)
|
|
1035
|
+
|
|
1034
1036
|
for (const event of voice.events) {
|
|
1035
1037
|
// Check for beam start/end in this event (including inside tuplets)
|
|
1036
1038
|
const { beamStart, beamEnd } = getEventBeamMarks(event);
|
|
1037
1039
|
|
|
1038
|
-
//
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1040
|
+
// Grace notes have independent beam groups - don't interfere with main beam
|
|
1041
|
+
const isGraceNote = event.type === 'note' && (event as NoteEvent).grace;
|
|
1042
|
+
|
|
1043
|
+
if (isGraceNote) {
|
|
1044
|
+
// Grace beam: open/close independently, nested inside parent beam if open
|
|
1045
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
1046
|
+
if (beamStart && !graceBeamOpen) {
|
|
1047
|
+
xml += `${graceBaseIndent}<beam xml:id="${generateId('beam')}">\n`;
|
|
1048
|
+
graceBeamOpen = true;
|
|
1049
|
+
}
|
|
1050
|
+
} else {
|
|
1051
|
+
// Close any open grace beam before processing a non-grace event
|
|
1052
|
+
if (graceBeamOpen) {
|
|
1053
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
1054
|
+
xml += `${graceBaseIndent}</beam>\n`;
|
|
1055
|
+
graceBeamOpen = false;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// Open main beam element if beam starts
|
|
1059
|
+
if (beamStart && !beamElementOpen) {
|
|
1060
|
+
xml += `${baseIndent}<beam xml:id="${generateId('beam')}">\n`;
|
|
1061
|
+
beamElementOpen = true;
|
|
1062
|
+
}
|
|
1042
1063
|
}
|
|
1043
1064
|
|
|
1044
|
-
const
|
|
1065
|
+
const graceIndentBase = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
1066
|
+
const currentIndent = graceBeamOpen ? graceIndentBase + ' ' : (beamElementOpen ? baseIndent + ' ' : baseIndent);
|
|
1045
1067
|
|
|
1046
1068
|
switch (event.type) {
|
|
1047
1069
|
case 'note': {
|
|
@@ -1292,12 +1314,26 @@ const encodeLayer = (voice: Voice, layerN: number, indent: string, initialTiePit
|
|
|
1292
1314
|
}
|
|
1293
1315
|
|
|
1294
1316
|
// Close beam element if beam ends
|
|
1295
|
-
if (beamEnd
|
|
1296
|
-
|
|
1297
|
-
|
|
1317
|
+
if (beamEnd) {
|
|
1318
|
+
if (isGraceNote && graceBeamOpen) {
|
|
1319
|
+
// Close grace beam
|
|
1320
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
1321
|
+
xml += `${graceBaseIndent}</beam>\n`;
|
|
1322
|
+
graceBeamOpen = false;
|
|
1323
|
+
} else if (!isGraceNote && beamElementOpen) {
|
|
1324
|
+
// Close main beam
|
|
1325
|
+
xml += `${baseIndent}</beam>\n`;
|
|
1326
|
+
beamElementOpen = false;
|
|
1327
|
+
}
|
|
1298
1328
|
}
|
|
1299
1329
|
}
|
|
1300
1330
|
|
|
1331
|
+
// Close any unclosed grace beam
|
|
1332
|
+
if (graceBeamOpen) {
|
|
1333
|
+
const graceBaseIndent = beamElementOpen ? baseIndent + ' ' : baseIndent;
|
|
1334
|
+
xml += `${graceBaseIndent}</beam>\n`;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1301
1337
|
// Close any unclosed beam
|
|
1302
1338
|
if (beamElementOpen) {
|
|
1303
1339
|
xml += `${baseIndent}</beam>\n`;
|