@k-l-lambda/lilylet 0.1.55 → 0.1.56
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/lilylet/grammar.jison.js +140 -134
- package/lib/lilylet/meiEncoder.js +26 -5
- package/lib/lilylet/types.d.ts +1 -0
- package/package.json +1 -1
- package/source/lilylet/grammar.jison.js +140 -134
- package/source/lilylet/lilylet.jison +3 -2
- package/source/lilylet/meiEncoder.ts +22 -5
- package/source/lilylet/types.ts +1 -0
|
@@ -140,7 +140,11 @@ const encodePitch = (pitch, keyFifths = 0, ottavaShift = 0, measureAccidentals)
|
|
|
140
140
|
const prevMeasureAccid = measureAccidentals?.get(pitchKey);
|
|
141
141
|
if (pitch.accidental) {
|
|
142
142
|
const noteAccid = ACCIDENTALS[pitch.accidental];
|
|
143
|
-
if (
|
|
143
|
+
if (pitch.courtesy) {
|
|
144
|
+
// Courtesy accidental (!) - always display
|
|
145
|
+
accid = noteAccid;
|
|
146
|
+
}
|
|
147
|
+
else if (prevMeasureAccid !== undefined && noteAccid !== prevMeasureAccid) {
|
|
144
148
|
// Previous note in this measure had a different accidental - must re-assert
|
|
145
149
|
accid = noteAccid;
|
|
146
150
|
}
|
|
@@ -156,7 +160,11 @@ const encodePitch = (pitch, keyFifths = 0, ottavaShift = 0, measureAccidentals)
|
|
|
156
160
|
}
|
|
157
161
|
else if (keyAccid) {
|
|
158
162
|
// Note has no accidental but key implies one - output natural
|
|
159
|
-
if (
|
|
163
|
+
if (pitch.courtesy) {
|
|
164
|
+
// Courtesy accidental (!) - always display
|
|
165
|
+
accid = 'n';
|
|
166
|
+
}
|
|
167
|
+
else if (prevMeasureAccid === 'n') {
|
|
160
168
|
// Already cancelled earlier in this measure - no need to show again
|
|
161
169
|
}
|
|
162
170
|
else {
|
|
@@ -166,6 +174,13 @@ const encodePitch = (pitch, keyFifths = 0, ottavaShift = 0, measureAccidentals)
|
|
|
166
174
|
if (measureAccidentals)
|
|
167
175
|
measureAccidentals.set(pitchKey, 'n');
|
|
168
176
|
}
|
|
177
|
+
else if (pitch.courtesy && prevMeasureAccid && prevMeasureAccid !== 'n') {
|
|
178
|
+
// Courtesy accidental after an in-measure accidental - force natural display
|
|
179
|
+
accid = 'n';
|
|
180
|
+
accidGes = 'n';
|
|
181
|
+
if (measureAccidentals)
|
|
182
|
+
measureAccidentals.set(pitchKey, 'n');
|
|
183
|
+
}
|
|
169
184
|
else if (measureAccidentals) {
|
|
170
185
|
// No explicit accidental, no key accidental - check if earlier note in measure had one
|
|
171
186
|
if (prevMeasureAccid && prevMeasureAccid !== 'n') {
|
|
@@ -483,11 +498,14 @@ const noteEventToMEI = (event, indent, layerStaff, tieEnd, contextStemDir, keyFi
|
|
|
483
498
|
};
|
|
484
499
|
};
|
|
485
500
|
// Convert RestEvent to MEI
|
|
486
|
-
const restEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0, measureAccidentals) => {
|
|
501
|
+
const restEventToMEI = (event, indent, keyFifths = 0, ottavaShift = 0, measureAccidentals, crossStaff) => {
|
|
487
502
|
const dur = DURATIONS[event.duration.division] || "4";
|
|
488
503
|
let attrs = `xml:id="${generateId('rest')}" dur="${dur}"`;
|
|
489
504
|
if (event.duration.dots > 0)
|
|
490
505
|
attrs += ` dots="${event.duration.dots}"`;
|
|
506
|
+
// Cross-staff attribute
|
|
507
|
+
if (crossStaff)
|
|
508
|
+
attrs += ` staff="${crossStaff}"`;
|
|
491
509
|
// Pitched rest (positioned at specific pitch)
|
|
492
510
|
if (event.pitch) {
|
|
493
511
|
const pitch = encodePitch(event.pitch, keyFifths, ottavaShift);
|
|
@@ -899,9 +917,12 @@ const encodeLayer = (voice, layerN, indent, initialTiePitches = [], keyFifths =
|
|
|
899
917
|
}
|
|
900
918
|
break;
|
|
901
919
|
}
|
|
902
|
-
case 'rest':
|
|
903
|
-
|
|
920
|
+
case 'rest': {
|
|
921
|
+
// For cross-staff notation: pass staff number if different from voice's home staff
|
|
922
|
+
const restCrossStaff = currentStaff !== (voice.staff || 1) ? currentStaff : undefined;
|
|
923
|
+
xml += restEventToMEI(event, currentIndent, keyFifths, currentOttavaShift, measureAccidentals, restCrossStaff);
|
|
904
924
|
break;
|
|
925
|
+
}
|
|
905
926
|
case 'tuplet': {
|
|
906
927
|
// Tuplet can be nested inside beam in MEI: <beam><tuplet>...</tuplet></beam>
|
|
907
928
|
// Pass beamElementOpen to tuplet so it knows not to create its own beam
|
package/lib/lilylet/types.d.ts
CHANGED
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.56",
|
|
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",
|