@k-l-lambda/lilylet 0.1.58 → 0.1.59
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.
|
@@ -451,7 +451,8 @@ const parseLilyDocument = (lilyDocument) => {
|
|
|
451
451
|
// Update key/time from context on music events
|
|
452
452
|
if (term instanceof lilyParser.MusicEvent ||
|
|
453
453
|
term instanceof lilyParser.LilyTerms.StemDirection ||
|
|
454
|
-
term instanceof lilyParser.LilyTerms.OctaveShift
|
|
454
|
+
term instanceof lilyParser.LilyTerms.OctaveShift ||
|
|
455
|
+
term instanceof lilyParser.LilyTerms.Change) {
|
|
455
456
|
if (context.key && measure.key === null) {
|
|
456
457
|
measure.key = context.key.key;
|
|
457
458
|
}
|
|
@@ -626,10 +627,21 @@ const parseLilyDocument = (lilyDocument) => {
|
|
|
626
627
|
lastOttava = term.value;
|
|
627
628
|
}
|
|
628
629
|
}
|
|
629
|
-
// Handle staff change
|
|
630
|
+
// Handle staff change (\change Staff = "N")
|
|
631
|
+
// args[0] is Assignment { key: "Staff", value: { exp: '"N"' } }
|
|
630
632
|
else if (term instanceof lilyParser.LilyTerms.Change) {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
+
const assignment = term.args?.[0];
|
|
634
|
+
if (assignment?.key === 'Staff') {
|
|
635
|
+
// exp is like '"2"' — strip surrounding quotes then parse strictly
|
|
636
|
+
const exp = assignment?.value?.exp ?? '';
|
|
637
|
+
const raw = exp.replace(/^"|"$/g, '');
|
|
638
|
+
// Only accept pure integer staff names (e.g. "1", "2")
|
|
639
|
+
// Reject compound names like "1_2", "RH", "LH"
|
|
640
|
+
const staffNum = /^\d+$/.test(raw) ? parseInt(raw, 10) : NaN;
|
|
641
|
+
if (!isNaN(staffNum)) {
|
|
642
|
+
voice.events.push({ type: 'context', staff: staffNum });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
633
645
|
}
|
|
634
646
|
// Handle tempo
|
|
635
647
|
else if (term instanceof lilyParser.LilyTerms.Tempo) {
|
|
@@ -439,10 +439,13 @@ const serializeEvent = (event, env, prevDuration) => {
|
|
|
439
439
|
};
|
|
440
440
|
// Find first clef in voice events
|
|
441
441
|
const findVoiceClef = (voice) => {
|
|
442
|
+
let activeStaff = voice.staff;
|
|
442
443
|
for (const event of voice.events) {
|
|
443
444
|
if (event.type === 'context') {
|
|
444
445
|
const ctx = event;
|
|
445
|
-
if (ctx.
|
|
446
|
+
if (ctx.staff)
|
|
447
|
+
activeStaff = ctx.staff;
|
|
448
|
+
if (ctx.clef && activeStaff === voice.staff) {
|
|
446
449
|
return ctx.clef;
|
|
447
450
|
}
|
|
448
451
|
}
|
|
@@ -504,10 +507,17 @@ const serializeVoice = (voice, currentStaff, isGrandStaff = false, measureContex
|
|
|
504
507
|
if (ctx.staff && ctx.staff !== activeStaff) {
|
|
505
508
|
activeStaff = ctx.staff;
|
|
506
509
|
parts.push('\\staff "' + activeStaff + '"');
|
|
510
|
+
// Emit target staff clef if the event carries one or allStaffClefs knows it
|
|
511
|
+
const ctxClef = ctx.clef || allStaffClefs?.[activeStaff];
|
|
512
|
+
if (ctxClef && emittedClefs?.[activeStaff] !== ctxClef) {
|
|
513
|
+
parts.push('\\clef "' + CLEF_MAP[ctxClef] + '"');
|
|
514
|
+
if (emittedClefs)
|
|
515
|
+
emittedClefs[activeStaff] = ctxClef;
|
|
516
|
+
}
|
|
507
517
|
continue;
|
|
508
518
|
}
|
|
509
519
|
if (ctx.staff)
|
|
510
|
-
continue; // same staff, no-op
|
|
520
|
+
continue; // same staff, no-op for staff field; clef handled below
|
|
511
521
|
// Skip clef-only context events if clef already established for this staff
|
|
512
522
|
if (clefOutputted && ctx.clef && !ctx.key && !ctx.time && !ctx.ottava && !ctx.stemDirection && !ctx.tempo) {
|
|
513
523
|
continue;
|
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.59",
|
|
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",
|
|
@@ -556,7 +556,8 @@ const parseLilyDocument = (lilyDocument: lilyParser.LilyDocument): ParsedMeasure
|
|
|
556
556
|
// Update key/time from context on music events
|
|
557
557
|
if (term instanceof lilyParser.MusicEvent ||
|
|
558
558
|
term instanceof lilyParser.LilyTerms.StemDirection ||
|
|
559
|
-
term instanceof lilyParser.LilyTerms.OctaveShift
|
|
559
|
+
term instanceof lilyParser.LilyTerms.OctaveShift ||
|
|
560
|
+
term instanceof lilyParser.LilyTerms.Change) {
|
|
560
561
|
|
|
561
562
|
if (context.key && measure.key === null) {
|
|
562
563
|
measure.key = context.key.key;
|
|
@@ -750,10 +751,21 @@ const parseLilyDocument = (lilyDocument: lilyParser.LilyDocument): ParsedMeasure
|
|
|
750
751
|
lastOttava = term.value;
|
|
751
752
|
}
|
|
752
753
|
}
|
|
753
|
-
// Handle staff change
|
|
754
|
+
// Handle staff change (\change Staff = "N")
|
|
755
|
+
// args[0] is Assignment { key: "Staff", value: { exp: '"N"' } }
|
|
754
756
|
else if (term instanceof lilyParser.LilyTerms.Change) {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
+
const assignment = (term as any).args?.[0];
|
|
758
|
+
if (assignment?.key === 'Staff') {
|
|
759
|
+
// exp is like '"2"' — strip surrounding quotes then parse strictly
|
|
760
|
+
const exp: string = assignment?.value?.exp ?? '';
|
|
761
|
+
const raw = exp.replace(/^"|"$/g, '');
|
|
762
|
+
// Only accept pure integer staff names (e.g. "1", "2")
|
|
763
|
+
// Reject compound names like "1_2", "RH", "LH"
|
|
764
|
+
const staffNum = /^\d+$/.test(raw) ? parseInt(raw, 10) : NaN;
|
|
765
|
+
if (!isNaN(staffNum)) {
|
|
766
|
+
voice.events.push({ type: 'context', staff: staffNum } as any);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
757
769
|
}
|
|
758
770
|
// Handle tempo
|
|
759
771
|
else if (term instanceof lilyParser.LilyTerms.Tempo) {
|
|
@@ -574,10 +574,12 @@ interface MeasureContext {
|
|
|
574
574
|
|
|
575
575
|
// Find first clef in voice events
|
|
576
576
|
const findVoiceClef = (voice: Voice): Clef | undefined => {
|
|
577
|
+
let activeStaff = voice.staff;
|
|
577
578
|
for (const event of voice.events) {
|
|
578
579
|
if (event.type === 'context') {
|
|
579
580
|
const ctx = event as ContextChange;
|
|
580
|
-
if (ctx.
|
|
581
|
+
if (ctx.staff) activeStaff = ctx.staff;
|
|
582
|
+
if (ctx.clef && activeStaff === voice.staff) {
|
|
581
583
|
return ctx.clef;
|
|
582
584
|
}
|
|
583
585
|
}
|
|
@@ -652,9 +654,15 @@ const serializeVoice = (
|
|
|
652
654
|
if (ctx.staff && ctx.staff !== activeStaff) {
|
|
653
655
|
activeStaff = ctx.staff;
|
|
654
656
|
parts.push('\\staff "' + activeStaff + '"');
|
|
657
|
+
// Emit target staff clef if the event carries one or allStaffClefs knows it
|
|
658
|
+
const ctxClef = ctx.clef || allStaffClefs?.[activeStaff];
|
|
659
|
+
if (ctxClef && emittedClefs?.[activeStaff] !== ctxClef) {
|
|
660
|
+
parts.push('\\clef "' + CLEF_MAP[ctxClef] + '"');
|
|
661
|
+
if (emittedClefs) emittedClefs[activeStaff] = ctxClef;
|
|
662
|
+
}
|
|
655
663
|
continue;
|
|
656
664
|
}
|
|
657
|
-
if (ctx.staff) continue; // same staff, no-op
|
|
665
|
+
if (ctx.staff) continue; // same staff, no-op for staff field; clef handled below
|
|
658
666
|
// Skip clef-only context events if clef already established for this staff
|
|
659
667
|
if (clefOutputted && ctx.clef && !ctx.key && !ctx.time && !ctx.ottava && !ctx.stemDirection && !ctx.tempo) {
|
|
660
668
|
continue;
|