@k-l-lambda/lilylet 0.1.78 → 0.1.79
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.
|
@@ -205,6 +205,51 @@ const renderRepeatSpan = (infos, lo, hi) => {
|
|
|
205
205
|
parts.push(rangeCode(lastStop + 1, hi));
|
|
206
206
|
return parts.join(", ");
|
|
207
207
|
};
|
|
208
|
+
// Render a whole piece that has one OR MORE plain repeat/volta sections in
|
|
209
|
+
// sequence (no navigation). Real scores are usually multi-section (AABB minuets,
|
|
210
|
+
// sonata exposition+recap), which the single-span renderRepeatSpan can't express
|
|
211
|
+
// — it bails when it sees >1 repeat-end. Here we cut the piece at section
|
|
212
|
+
// boundaries (each cut ends a repeat section, voltas included) and render each
|
|
213
|
+
// span independently, joining with commas. Any non-repeated measures between two
|
|
214
|
+
// sections are absorbed as the `pre` of the following span. Returns null if any
|
|
215
|
+
// section isn't a clean single repeat/volta (caller then falls back to flat).
|
|
216
|
+
const renderRepeatSections = (infos, total) => {
|
|
217
|
+
const repeatEnds = infos.filter(i => i.repeatEnd).map(i => i.index).sort((a, b) => a - b);
|
|
218
|
+
const repeatStarts = infos.filter(i => i.repeatStart).map(i => i.index).sort((a, b) => a - b);
|
|
219
|
+
if (repeatEnds.length <= 1)
|
|
220
|
+
return renderRepeatSpan(infos, 1, total);
|
|
221
|
+
const endingStops = infos.filter(i => i.endingStop !== undefined).map(i => i.index);
|
|
222
|
+
const parts = [];
|
|
223
|
+
let prevHi = 0;
|
|
224
|
+
for (const e of repeatEnds) {
|
|
225
|
+
const lo = prevHi + 1;
|
|
226
|
+
// The next section's repeat-start bounds this section's voltas (2nd/3rd
|
|
227
|
+
// endings sit AFTER the repeat-end but BEFORE the next section starts).
|
|
228
|
+
const nextStart = repeatStarts.find(s => s > e);
|
|
229
|
+
const sectionEndingStarts = infos
|
|
230
|
+
.filter(i => i.endingStart !== undefined && i.index >= lo && (nextStart === undefined || i.index < nextStart))
|
|
231
|
+
.map(i => i.index)
|
|
232
|
+
.sort((a, b) => a - b);
|
|
233
|
+
let hi;
|
|
234
|
+
if (sectionEndingStarts.length) {
|
|
235
|
+
const lastStart = sectionEndingStarts[sectionEndingStarts.length - 1];
|
|
236
|
+
// the last ending's stop closes the section; else the ending start itself
|
|
237
|
+
const stop = endingStops.filter(s => s >= lastStart).sort((a, b) => a - b)[0];
|
|
238
|
+
hi = stop ?? lastStart;
|
|
239
|
+
}
|
|
240
|
+
else
|
|
241
|
+
hi = e;
|
|
242
|
+
const code = renderRepeatSpan(infos, lo, hi);
|
|
243
|
+
if (code === null)
|
|
244
|
+
return null;
|
|
245
|
+
parts.push(code);
|
|
246
|
+
prevHi = hi;
|
|
247
|
+
}
|
|
248
|
+
// trailing non-repeated tail
|
|
249
|
+
if (prevHi < total)
|
|
250
|
+
parts.push(rangeCode(prevHi + 1, total));
|
|
251
|
+
return parts.join(", ");
|
|
252
|
+
};
|
|
208
253
|
const tryStructured = (infos, total) => {
|
|
209
254
|
const dacapo = infos.find(i => i.dacapo);
|
|
210
255
|
const dalsegno = infos.some(i => i.dalsegno);
|
|
@@ -223,8 +268,10 @@ const tryStructured = (infos, total) => {
|
|
|
223
268
|
}
|
|
224
269
|
if (dalsegno || tocoda || infos.some(i => i.fine || i.segno || i.coda))
|
|
225
270
|
return null;
|
|
226
|
-
// No navigation:
|
|
227
|
-
|
|
271
|
+
// No navigation: one or more plain repeat/volta sections in sequence. Most
|
|
272
|
+
// real pieces (AABB minuets, sonata exposition+recap) have ≥2 repeat sections;
|
|
273
|
+
// render each independently and join with commas.
|
|
274
|
+
return renderRepeatSections(infos, total);
|
|
228
275
|
};
|
|
229
276
|
// Construct the ABA form <main, rest> for a D.C. case. main = the volta/repeat
|
|
230
277
|
// structure; rest = the post-volta tail before the D.C. measure. The Once
|
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.79",
|
|
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",
|
|
@@ -214,6 +214,52 @@ const renderRepeatSpan = (infos: MeasureRepeatInfo[], lo: number, hi: number): s
|
|
|
214
214
|
return parts.join(", ");
|
|
215
215
|
};
|
|
216
216
|
|
|
217
|
+
// Render a whole piece that has one OR MORE plain repeat/volta sections in
|
|
218
|
+
// sequence (no navigation). Real scores are usually multi-section (AABB minuets,
|
|
219
|
+
// sonata exposition+recap), which the single-span renderRepeatSpan can't express
|
|
220
|
+
// — it bails when it sees >1 repeat-end. Here we cut the piece at section
|
|
221
|
+
// boundaries (each cut ends a repeat section, voltas included) and render each
|
|
222
|
+
// span independently, joining with commas. Any non-repeated measures between two
|
|
223
|
+
// sections are absorbed as the `pre` of the following span. Returns null if any
|
|
224
|
+
// section isn't a clean single repeat/volta (caller then falls back to flat).
|
|
225
|
+
const renderRepeatSections = (infos: MeasureRepeatInfo[], total: number): string | null => {
|
|
226
|
+
const repeatEnds = infos.filter(i => i.repeatEnd).map(i => i.index).sort((a, b) => a - b);
|
|
227
|
+
const repeatStarts = infos.filter(i => i.repeatStart).map(i => i.index).sort((a, b) => a - b);
|
|
228
|
+
if (repeatEnds.length <= 1) return renderRepeatSpan(infos, 1, total);
|
|
229
|
+
|
|
230
|
+
const endingStops = infos.filter(i => i.endingStop !== undefined).map(i => i.index);
|
|
231
|
+
|
|
232
|
+
const parts: string[] = [];
|
|
233
|
+
let prevHi = 0;
|
|
234
|
+
for (const e of repeatEnds) {
|
|
235
|
+
const lo = prevHi + 1;
|
|
236
|
+
// The next section's repeat-start bounds this section's voltas (2nd/3rd
|
|
237
|
+
// endings sit AFTER the repeat-end but BEFORE the next section starts).
|
|
238
|
+
const nextStart = repeatStarts.find(s => s > e);
|
|
239
|
+
const sectionEndingStarts = infos
|
|
240
|
+
.filter(i => i.endingStart !== undefined && i.index >= lo && (nextStart === undefined || i.index < nextStart))
|
|
241
|
+
.map(i => i.index)
|
|
242
|
+
.sort((a, b) => a - b);
|
|
243
|
+
|
|
244
|
+
let hi: number;
|
|
245
|
+
if (sectionEndingStarts.length) {
|
|
246
|
+
const lastStart = sectionEndingStarts[sectionEndingStarts.length - 1];
|
|
247
|
+
// the last ending's stop closes the section; else the ending start itself
|
|
248
|
+
const stop = endingStops.filter(s => s >= lastStart).sort((a, b) => a - b)[0];
|
|
249
|
+
hi = stop ?? lastStart;
|
|
250
|
+
}
|
|
251
|
+
else hi = e;
|
|
252
|
+
|
|
253
|
+
const code = renderRepeatSpan(infos, lo, hi);
|
|
254
|
+
if (code === null) return null;
|
|
255
|
+
parts.push(code);
|
|
256
|
+
prevHi = hi;
|
|
257
|
+
}
|
|
258
|
+
// trailing non-repeated tail
|
|
259
|
+
if (prevHi < total) parts.push(rangeCode(prevHi + 1, total));
|
|
260
|
+
return parts.join(", ");
|
|
261
|
+
};
|
|
262
|
+
|
|
217
263
|
const tryStructured = (infos: MeasureRepeatInfo[], total: number): string | null => {
|
|
218
264
|
const dacapo = infos.find(i => i.dacapo);
|
|
219
265
|
const dalsegno = infos.some(i => i.dalsegno);
|
|
@@ -233,8 +279,10 @@ const tryStructured = (infos: MeasureRepeatInfo[], total: number): string | null
|
|
|
233
279
|
}
|
|
234
280
|
if (dalsegno || tocoda || infos.some(i => i.fine || i.segno || i.coda)) return null;
|
|
235
281
|
|
|
236
|
-
// No navigation:
|
|
237
|
-
|
|
282
|
+
// No navigation: one or more plain repeat/volta sections in sequence. Most
|
|
283
|
+
// real pieces (AABB minuets, sonata exposition+recap) have ≥2 repeat sections;
|
|
284
|
+
// render each independently and join with commas.
|
|
285
|
+
return renderRepeatSections(infos, total);
|
|
238
286
|
};
|
|
239
287
|
|
|
240
288
|
// Construct the ABA form <main, rest> for a D.C. case. main = the volta/repeat
|