@k-l-lambda/lilylet 0.1.34 → 0.1.35

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
@@ -1237,9 +1237,11 @@ const analyzePartStructure = (doc) => {
1237
1237
  return partInfos;
1238
1238
  };
1239
1239
  // Encode scoreDef with part groups
1240
- const encodeScoreDef = (keySig, timeNum, timeDen, partInfos, indent) => {
1240
+ const encodeScoreDef = (keySig, timeNum, timeDen, partInfos, indent, meterSymbol) => {
1241
1241
  const scoreDefId = generateId("scoredef");
1242
- let xml = `${indent}<scoreDef xml:id="${scoreDefId}" key.sig="${keySig}" meter.count="${timeNum}" meter.unit="${timeDen}">\n`;
1242
+ // Build meter attributes
1243
+ const meterSymAttr = meterSymbol ? ` meter.sym="${meterSymbol}"` : '';
1244
+ let xml = `${indent}<scoreDef xml:id="${scoreDefId}" key.sig="${keySig}"${meterSymAttr} meter.count="${timeNum}" meter.unit="${timeDen}">\n`;
1243
1245
  xml += `${indent} <staffGrp xml:id="${generateId("staffgrp")}">\n`;
1244
1246
  for (let pi = 0; pi < partInfos.length; pi++) {
1245
1247
  const info = partInfos[pi];
@@ -1281,6 +1283,7 @@ const encode = (doc, options = {}) => {
1281
1283
  let currentKey = 0;
1282
1284
  let currentTimeNum = 4;
1283
1285
  let currentTimeDen = 4;
1286
+ let currentMeterSymbol = undefined;
1284
1287
  const firstMeasure = doc.measures[0];
1285
1288
  if (firstMeasure.key) {
1286
1289
  currentKey = keyToFifths(firstMeasure.key);
@@ -1288,6 +1291,7 @@ const encode = (doc, options = {}) => {
1288
1291
  if (firstMeasure.timeSig) {
1289
1292
  currentTimeNum = firstMeasure.timeSig.numerator;
1290
1293
  currentTimeDen = firstMeasure.timeSig.denominator;
1294
+ currentMeterSymbol = firstMeasure.timeSig.symbol;
1291
1295
  }
1292
1296
  const keySig = KEY_SIGS[currentKey] || "0";
1293
1297
  // Build MEI document
@@ -1332,7 +1336,7 @@ const encode = (doc, options = {}) => {
1332
1336
  mei += `${indent}${indent}<body>\n`;
1333
1337
  mei += `${indent}${indent}${indent}<mdiv xml:id="${generateId("mdiv")}">\n`;
1334
1338
  mei += `${indent}${indent}${indent}${indent}<score xml:id="${generateId("score")}">\n`;
1335
- mei += encodeScoreDef(keySig, currentTimeNum, currentTimeDen, partInfos, `${indent}${indent}${indent}${indent}${indent}`);
1339
+ mei += encodeScoreDef(keySig, currentTimeNum, currentTimeDen, partInfos, `${indent}${indent}${indent}${indent}${indent}`, currentMeterSymbol);
1336
1340
  mei += `${indent}${indent}${indent}${indent}${indent}<section xml:id="${generateId("section")}">\n`;
1337
1341
  // Track tie state across measures for cross-measure ties
1338
1342
  const tieState = {};
@@ -1381,6 +1385,20 @@ const encode = (doc, options = {}) => {
1381
1385
  mei += `${indent}${indent}${indent}${indent}${indent}${indent}<scoreDef xml:id="${generateId('scoredef')}" key.sig="${newKeySig}" />\n`;
1382
1386
  }
1383
1387
  }
1388
+ // Check for time signature change and output scoreDef if needed
1389
+ if (measure.timeSig && mi > 0) {
1390
+ const newTimeNum = measure.timeSig.numerator;
1391
+ const newTimeDen = measure.timeSig.denominator;
1392
+ const newMeterSymbol = measure.timeSig.symbol;
1393
+ if (newTimeNum !== currentTimeNum || newTimeDen !== currentTimeDen || newMeterSymbol !== currentMeterSymbol) {
1394
+ currentTimeNum = newTimeNum;
1395
+ currentTimeDen = newTimeDen;
1396
+ currentMeterSymbol = newMeterSymbol;
1397
+ // Output a scoreDef with the new time signature
1398
+ const meterSymAttr = currentMeterSymbol ? ` meter.sym="${currentMeterSymbol}"` : '';
1399
+ mei += `${indent}${indent}${indent}${indent}${indent}${indent}<scoreDef xml:id="${generateId('scoredef')}"${meterSymAttr} meter.count="${currentTimeNum}" meter.unit="${currentTimeDen}" />\n`;
1400
+ }
1401
+ }
1384
1402
  mei += encodeMeasure(measure, mi + 1, `${indent}${indent}${indent}${indent}${indent}${indent}`, totalStaves, tieState, slurState, hairpinState, currentKey, partInfos, clefState);
1385
1403
  });
1386
1404
  mei += `${indent}${indent}${indent}${indent}${indent}</section>\n`;
package/lib/serializer.js CHANGED
@@ -448,7 +448,13 @@ const serializeVoice = (voice, currentStaff, isGrandStaff = false, measureContex
448
448
  parts.push('\\key ' + keyStr);
449
449
  }
450
450
  if (measureContext.time) {
451
- parts.push('\\time ' + measureContext.time.numerator + '/' + measureContext.time.denominator);
451
+ const { numerator, denominator, symbol } = measureContext.time;
452
+ // Output \numericTimeSignature before 4/4 or 2/2 if no symbol is set
453
+ // (meaning numeric display was explicitly requested)
454
+ if (!symbol && ((numerator === 4 && denominator === 4) || (numerator === 2 && denominator === 2))) {
455
+ parts.push('\\numericTimeSignature');
456
+ }
457
+ parts.push('\\time ' + numerator + '/' + denominator);
452
458
  }
453
459
  }
454
460
  for (const event of voice.events) {
package/lib/types.d.ts CHANGED
@@ -83,6 +83,9 @@ export interface Fraction {
83
83
  numerator: number;
84
84
  denominator: number;
85
85
  }
86
+ export interface TimeSig extends Fraction {
87
+ symbol?: 'common' | 'cut';
88
+ }
86
89
  export interface Pitch {
87
90
  phonet: Phonet;
88
91
  accidental?: Accidental;
@@ -231,7 +234,7 @@ export interface Part {
231
234
  }
232
235
  export interface Measure {
233
236
  key?: KeySignature;
234
- timeSig?: Fraction;
237
+ timeSig?: TimeSig;
235
238
  parts: Part[];
236
239
  partial?: boolean;
237
240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@k-l-lambda/lilylet",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
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",