@k-l-lambda/lilylet 0.1.57 → 0.1.58

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.
@@ -585,6 +585,7 @@ const parseLilyDocument = (lilyDocument) => {
585
585
  const restEvent = {
586
586
  type: 'rest',
587
587
  duration: convertDuration(term.durationValue),
588
+ fullMeasure: (term.name === 'R') || undefined,
588
589
  invisible: term.isSpacer || undefined,
589
590
  };
590
591
  voice.events.push(restEvent);
@@ -500,10 +500,14 @@ const serializeVoice = (voice, currentStaff, isGrandStaff = false, measureContex
500
500
  for (const event of voice.events) {
501
501
  if (event.type === 'context') {
502
502
  const ctx = event;
503
- // Skip context events that belong to a different staff (cross-staff clef/ottava)
504
- if (ctx.staff && ctx.staff !== voice.staff) {
503
+ // Cross-staff context: update activeStaff and emit \staff directive
504
+ if (ctx.staff && ctx.staff !== activeStaff) {
505
+ activeStaff = ctx.staff;
506
+ parts.push('\\staff "' + activeStaff + '"');
505
507
  continue;
506
508
  }
509
+ if (ctx.staff)
510
+ continue; // same staff, no-op
507
511
  // Skip clef-only context events if clef already established for this staff
508
512
  if (clefOutputted && ctx.clef && !ctx.key && !ctx.time && !ctx.ottava && !ctx.stemDirection && !ctx.tempo) {
509
513
  continue;
@@ -511,8 +515,8 @@ const serializeVoice = (voice, currentStaff, isGrandStaff = false, measureContex
511
515
  }
512
516
  if (event.type === 'note') {
513
517
  const noteEvt = event;
514
- // Cross-staff: emit \staff when note's effective staff differs from active
515
- const effectiveStaff = noteEvt.staff || voice.staff;
518
+ // Cross-staff via explicit note.staff (lilylet native cross-staff)
519
+ const effectiveStaff = noteEvt.staff || activeStaff;
516
520
  if (effectiveStaff !== activeStaff) {
517
521
  activeStaff = effectiveStaff;
518
522
  parts.push('\\staff "' + activeStaff + '"');
@@ -679,12 +683,16 @@ export const serializeLilyletDoc = (doc) => {
679
683
  // Collect clefs from this measure's voices
680
684
  for (const part of measure.parts) {
681
685
  for (const voice of part.voices) {
686
+ let clefActiveStaff = voice.staff;
682
687
  for (const event of voice.events) {
683
- if (event.type === 'context' && event.clef) {
688
+ if (event.type === 'context') {
684
689
  const ctx = event;
685
- // Use the event's staff if specified (cross-staff), otherwise the voice's staff
686
- const clefStaff = ctx.staff || voice.staff;
687
- staffClefs[clefStaff] = ctx.clef;
690
+ if (ctx.staff) {
691
+ clefActiveStaff = ctx.staff;
692
+ }
693
+ if (ctx.clef) {
694
+ staffClefs[clefActiveStaff] = ctx.clef;
695
+ }
688
696
  }
689
697
  }
690
698
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-l-lambda/lilylet",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
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",
@@ -708,6 +708,7 @@ const parseLilyDocument = (lilyDocument: lilyParser.LilyDocument): ParsedMeasure
708
708
  const restEvent: RestEvent = {
709
709
  type: 'rest',
710
710
  duration: convertDuration(term.durationValue),
711
+ fullMeasure: (term.name === 'R') || undefined,
711
712
  invisible: term.isSpacer || undefined,
712
713
  };
713
714
 
@@ -648,10 +648,13 @@ const serializeVoice = (
648
648
  for (const event of voice.events) {
649
649
  if (event.type === 'context') {
650
650
  const ctx = event as ContextChange;
651
- // Skip context events that belong to a different staff (cross-staff clef/ottava)
652
- if (ctx.staff && ctx.staff !== voice.staff) {
651
+ // Cross-staff context: update activeStaff and emit \staff directive
652
+ if (ctx.staff && ctx.staff !== activeStaff) {
653
+ activeStaff = ctx.staff;
654
+ parts.push('\\staff "' + activeStaff + '"');
653
655
  continue;
654
656
  }
657
+ if (ctx.staff) continue; // same staff, no-op
655
658
  // Skip clef-only context events if clef already established for this staff
656
659
  if (clefOutputted && ctx.clef && !ctx.key && !ctx.time && !ctx.ottava && !ctx.stemDirection && !ctx.tempo) {
657
660
  continue;
@@ -661,8 +664,8 @@ const serializeVoice = (
661
664
  if (event.type === 'note') {
662
665
  const noteEvt = event as NoteEvent;
663
666
 
664
- // Cross-staff: emit \staff when note's effective staff differs from active
665
- const effectiveStaff = noteEvt.staff || voice.staff;
667
+ // Cross-staff via explicit note.staff (lilylet native cross-staff)
668
+ const effectiveStaff = noteEvt.staff || activeStaff;
666
669
  if (effectiveStaff !== activeStaff) {
667
670
  activeStaff = effectiveStaff;
668
671
  parts.push('\\staff "' + activeStaff + '"');
@@ -872,12 +875,16 @@ export const serializeLilyletDoc = (doc: LilyletDoc): string => {
872
875
  // Collect clefs from this measure's voices
873
876
  for (const part of measure.parts) {
874
877
  for (const voice of part.voices) {
878
+ let clefActiveStaff = voice.staff;
875
879
  for (const event of voice.events) {
876
- if (event.type === 'context' && (event as ContextChange).clef) {
880
+ if (event.type === 'context') {
877
881
  const ctx = event as ContextChange;
878
- // Use the event's staff if specified (cross-staff), otherwise the voice's staff
879
- const clefStaff = ctx.staff || voice.staff;
880
- staffClefs[clefStaff] = ctx.clef!;
882
+ if (ctx.staff) {
883
+ clefActiveStaff = ctx.staff;
884
+ }
885
+ if (ctx.clef) {
886
+ staffClefs[clefActiveStaff] = ctx.clef;
887
+ }
881
888
  }
882
889
  }
883
890
  }