@k-l-lambda/lilylet 0.1.31 → 0.1.33

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/meiEncoder.js CHANGED
@@ -128,20 +128,24 @@ const encodePitch = (pitch, keyFifths = 0, ottavaShift = 0) => {
128
128
  // Get the accidental implied by the key signature for this note
129
129
  const keyAccidentals = getKeyAccidentals(keyFifths);
130
130
  const keyAccid = keyAccidentals[pitch.phonet];
131
- // Determine if we need to output an accid attribute
131
+ // Determine accid (written/displayed) and accid.ges (gestural/sounding)
132
132
  let accid;
133
+ let accidGes;
133
134
  if (pitch.accidental) {
134
135
  const noteAccid = ACCIDENTALS[pitch.accidental];
135
- // Only output accid if it's different from what the key implies
136
136
  if (noteAccid !== keyAccid) {
137
+ // Accidental differs from key signature - display it
137
138
  accid = noteAccid;
138
139
  }
140
+ // Always set gestural accidental for MIDI generation
141
+ accidGes = noteAccid;
139
142
  }
140
143
  else if (keyAccid) {
141
144
  // Note has no accidental but key implies one - output natural
142
145
  accid = 'n';
146
+ accidGes = 'n';
143
147
  }
144
- return { pname: pitch.phonet, oct, accid };
148
+ return { pname: pitch.phonet, oct, accid, accidGes };
145
149
  };
146
150
  // Convert tremolo division to stem.mod value
147
151
  const tremoloToStemMod = (division) => {
@@ -161,6 +165,8 @@ const buildNoteElement = (pitch, dur, dots, indent, inChord, options = {}, noteI
161
165
  }
162
166
  if (pitch.accid)
163
167
  attrs += ` accid="${pitch.accid}"`;
168
+ if (pitch.accidGes)
169
+ attrs += ` accid.ges="${pitch.accidGes}"`;
164
170
  if (!inChord && dots > 0)
165
171
  attrs += ` dots="${dots}"`;
166
172
  if (!inChord && options.grace)
@@ -566,6 +572,8 @@ const tremoloEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0) => {
566
572
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}" dur="${noteDur}"`;
567
573
  if (pitch.accid)
568
574
  attrs += ` accid="${pitch.accid}"`;
575
+ if (pitch.accidGes)
576
+ attrs += ` accid.ges="${pitch.accidGes}"`;
569
577
  result += `${indent} <note ${attrs} />\n`;
570
578
  }
571
579
  else if (event.pitchA.length > 1) {
@@ -575,6 +583,8 @@ const tremoloEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0) => {
575
583
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}"`;
576
584
  if (pitch.accid)
577
585
  attrs += ` accid="${pitch.accid}"`;
586
+ if (pitch.accidGes)
587
+ attrs += ` accid.ges="${pitch.accidGes}"`;
578
588
  result += `${indent} <note ${attrs} />\n`;
579
589
  }
580
590
  result += `${indent} </chord>\n`;
@@ -585,6 +595,8 @@ const tremoloEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0) => {
585
595
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}" dur="${noteDur}"`;
586
596
  if (pitch.accid)
587
597
  attrs += ` accid="${pitch.accid}"`;
598
+ if (pitch.accidGes)
599
+ attrs += ` accid.ges="${pitch.accidGes}"`;
588
600
  result += `${indent} <note ${attrs} />\n`;
589
601
  }
590
602
  else if (event.pitchB.length > 1) {
@@ -594,6 +606,8 @@ const tremoloEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0) => {
594
606
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}"`;
595
607
  if (pitch.accid)
596
608
  attrs += ` accid="${pitch.accid}"`;
609
+ if (pitch.accidGes)
610
+ attrs += ` accid.ges="${pitch.accidGes}"`;
597
611
  result += `${indent} <note ${attrs} />\n`;
598
612
  }
599
613
  result += `${indent} </chord>\n`;
@@ -1360,9 +1374,15 @@ const encode = (doc, options = {}) => {
1360
1374
  }
1361
1375
  // Encode measures
1362
1376
  measures.forEach((measure, mi) => {
1363
- // Update key signature if measure has one
1377
+ // Check for key signature change and output scoreDef if needed
1364
1378
  if (measure.key) {
1365
- currentKey = keyToFifths(measure.key);
1379
+ const newKey = keyToFifths(measure.key);
1380
+ if (newKey !== currentKey) {
1381
+ currentKey = newKey;
1382
+ const newKeySig = KEY_SIGS[currentKey] || "0";
1383
+ // Output a scoreDef with the new key signature
1384
+ mei += `${indent}${indent}${indent}${indent}${indent}${indent}<scoreDef xml:id="${generateId('scoredef')}" key.sig="${newKeySig}" />\n`;
1385
+ }
1366
1386
  }
1367
1387
  mei += encodeMeasure(measure, mi + 1, `${indent}${indent}${indent}${indent}${indent}${indent}`, totalStaves, tieState, slurState, hairpinState, currentKey, partInfos, clefState);
1368
1388
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-l-lambda/lilylet",
3
- "version": "0.1.31",
3
+ "version": "0.1.33",
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",
@@ -171,7 +171,7 @@ const getKeyAccidentals = (fifths: number): Record<string, string> => {
171
171
  // Convert Pitch to MEI attributes, checking against key signature
172
172
  // ottavaShift: current ottava level (1 = 8va up, -1 = 8vb down, 2 = 15ma up, etc.)
173
173
  // The written pitch should be adjusted by subtracting the ottava shift
174
- const encodePitch = (pitch: Pitch, keyFifths: number = 0, ottavaShift: number = 0): { pname: string; oct: number; accid?: string } => {
174
+ const encodePitch = (pitch: Pitch, keyFifths: number = 0, ottavaShift: number = 0): { pname: string; oct: number; accid?: string; accidGes?: string } => {
175
175
  // Lilylet octave: 0 = middle C octave (C4), positive = higher, negative = lower
176
176
  // When ottava is active, the source pitch is the sounding pitch, but we need to output the written pitch
177
177
  // For 8va up (ottavaShift=1), written pitch is one octave lower than sounding
@@ -181,20 +181,25 @@ const encodePitch = (pitch: Pitch, keyFifths: number = 0, ottavaShift: number =
181
181
  const keyAccidentals = getKeyAccidentals(keyFifths);
182
182
  const keyAccid = keyAccidentals[pitch.phonet];
183
183
 
184
- // Determine if we need to output an accid attribute
184
+ // Determine accid (written/displayed) and accid.ges (gestural/sounding)
185
185
  let accid: string | undefined;
186
+ let accidGes: string | undefined;
187
+
186
188
  if (pitch.accidental) {
187
189
  const noteAccid = ACCIDENTALS[pitch.accidental];
188
- // Only output accid if it's different from what the key implies
189
190
  if (noteAccid !== keyAccid) {
191
+ // Accidental differs from key signature - display it
190
192
  accid = noteAccid;
191
193
  }
194
+ // Always set gestural accidental for MIDI generation
195
+ accidGes = noteAccid;
192
196
  } else if (keyAccid) {
193
197
  // Note has no accidental but key implies one - output natural
194
198
  accid = 'n';
199
+ accidGes = 'n';
195
200
  }
196
201
 
197
- return { pname: pitch.phonet, oct, accid };
202
+ return { pname: pitch.phonet, oct, accid, accidGes };
198
203
  };
199
204
 
200
205
 
@@ -210,7 +215,7 @@ const tremoloToStemMod = (division: number): string | undefined => {
210
215
 
211
216
  // Build note element
212
217
  const buildNoteElement = (
213
- pitch: { pname: string; oct: number; accid?: string },
218
+ pitch: { pname: string; oct: number; accid?: string; accidGes?: string },
214
219
  dur: string,
215
220
  dots: number,
216
221
  indent: string,
@@ -233,6 +238,7 @@ const buildNoteElement = (
233
238
  attrs += ` dur="${dur}"`;
234
239
  }
235
240
  if (pitch.accid) attrs += ` accid="${pitch.accid}"`;
241
+ if (pitch.accidGes) attrs += ` accid.ges="${pitch.accidGes}"`;
236
242
  if (!inChord && dots > 0) attrs += ` dots="${dots}"`;
237
243
  if (!inChord && options.grace) attrs += ` grace="unacc"`;
238
244
  if (!inChord && options.tie) attrs += ` tie="${options.tie}"`;
@@ -715,6 +721,7 @@ const tremoloEventToMEI = (event: TremoloEvent, indent: string, keyFifths: numbe
715
721
  const pitch = encodePitch(event.pitchA[0], keyFifths, ottavaShift);
716
722
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}" dur="${noteDur}"`;
717
723
  if (pitch.accid) attrs += ` accid="${pitch.accid}"`;
724
+ if (pitch.accidGes) attrs += ` accid.ges="${pitch.accidGes}"`;
718
725
  result += `${indent} <note ${attrs} />\n`;
719
726
  } else if (event.pitchA.length > 1) {
720
727
  result += `${indent} <chord xml:id="${generateId('chord')}" dur="${noteDur}">\n`;
@@ -722,6 +729,7 @@ const tremoloEventToMEI = (event: TremoloEvent, indent: string, keyFifths: numbe
722
729
  const pitch = encodePitch(p, keyFifths, ottavaShift);
723
730
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}"`;
724
731
  if (pitch.accid) attrs += ` accid="${pitch.accid}"`;
732
+ if (pitch.accidGes) attrs += ` accid.ges="${pitch.accidGes}"`;
725
733
  result += `${indent} <note ${attrs} />\n`;
726
734
  }
727
735
  result += `${indent} </chord>\n`;
@@ -732,6 +740,7 @@ const tremoloEventToMEI = (event: TremoloEvent, indent: string, keyFifths: numbe
732
740
  const pitch = encodePitch(event.pitchB[0], keyFifths, ottavaShift);
733
741
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}" dur="${noteDur}"`;
734
742
  if (pitch.accid) attrs += ` accid="${pitch.accid}"`;
743
+ if (pitch.accidGes) attrs += ` accid.ges="${pitch.accidGes}"`;
735
744
  result += `${indent} <note ${attrs} />\n`;
736
745
  } else if (event.pitchB.length > 1) {
737
746
  result += `${indent} <chord xml:id="${generateId('chord')}" dur="${noteDur}">\n`;
@@ -739,6 +748,7 @@ const tremoloEventToMEI = (event: TremoloEvent, indent: string, keyFifths: numbe
739
748
  const pitch = encodePitch(p, keyFifths, ottavaShift);
740
749
  let attrs = `xml:id="${generateId('note')}" pname="${pitch.pname}" oct="${pitch.oct}"`;
741
750
  if (pitch.accid) attrs += ` accid="${pitch.accid}"`;
751
+ if (pitch.accidGes) attrs += ` accid.ges="${pitch.accidGes}"`;
742
752
  result += `${indent} <note ${attrs} />\n`;
743
753
  }
744
754
  result += `${indent} </chord>\n`;
@@ -1744,9 +1754,15 @@ const encode = (doc: LilyletDoc, options: MEIEncoderOptions = {}): string => {
1744
1754
 
1745
1755
  // Encode measures
1746
1756
  measures.forEach((measure, mi) => {
1747
- // Update key signature if measure has one
1757
+ // Check for key signature change and output scoreDef if needed
1748
1758
  if (measure.key) {
1749
- currentKey = keyToFifths(measure.key);
1759
+ const newKey = keyToFifths(measure.key);
1760
+ if (newKey !== currentKey) {
1761
+ currentKey = newKey;
1762
+ const newKeySig = KEY_SIGS[currentKey] || "0";
1763
+ // Output a scoreDef with the new key signature
1764
+ mei += `${indent}${indent}${indent}${indent}${indent}${indent}<scoreDef xml:id="${generateId('scoredef')}" key.sig="${newKeySig}" />\n`;
1765
+ }
1750
1766
  }
1751
1767
  mei += encodeMeasure(measure, mi + 1, `${indent}${indent}${indent}${indent}${indent}${indent}`, totalStaves, tieState, slurState, hairpinState, currentKey, partInfos, clefState);
1752
1768
  });