musicxml-io 0.3.4 → 0.3.5

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.
@@ -191,7 +191,7 @@ function groupByVoice(measure) {
191
191
  for (const entry of measure.entries) {
192
192
  if (entry.type !== "note") continue;
193
193
  const staff = _nullishCoalesce(entry.staff, () => ( 1));
194
- const voice = _nullishCoalesce(entry.voice, () => ( 1));
194
+ const voice = _nullishCoalesce(entry.voice, () => ( "1"));
195
195
  const key = `${staff}-${voice}`;
196
196
  if (!groups.has(key)) {
197
197
  groups.set(key, { staff, voice, notes: [] });
@@ -200,7 +200,7 @@ function groupByVoice(measure) {
200
200
  }
201
201
  return Array.from(groups.values()).sort((a, b) => {
202
202
  if (a.staff !== b.staff) return a.staff - b.staff;
203
- return a.voice - b.voice;
203
+ return a.voice.localeCompare(b.voice, void 0, { numeric: true });
204
204
  });
205
205
  }
206
206
  function groupByStaff(measure) {
@@ -285,10 +285,10 @@ function getVoices(measure) {
285
285
  const voices = /* @__PURE__ */ new Set();
286
286
  for (const entry of measure.entries) {
287
287
  if (entry.type === "note") {
288
- voices.add(_nullishCoalesce(entry.voice, () => ( 1)));
288
+ voices.add(_nullishCoalesce(entry.voice, () => ( "1")));
289
289
  }
290
290
  }
291
- return Array.from(voices).sort((a, b) => a - b);
291
+ return Array.from(voices).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
292
292
  }
293
293
  function getStaves(measure) {
294
294
  const staves = /* @__PURE__ */ new Set();
@@ -336,7 +336,7 @@ function buildVoiceToStaffMap(measure) {
336
336
  const map = /* @__PURE__ */ new Map();
337
337
  for (const entry of measure.entries) {
338
338
  if (entry.type === "note" && entry.staff !== void 0) {
339
- const voice = _nullishCoalesce(entry.voice, () => ( 1));
339
+ const voice = _nullishCoalesce(entry.voice, () => ( "1"));
340
340
  const staff = entry.staff;
341
341
  if (!map.has(voice)) {
342
342
  map.set(voice, staff);
@@ -355,7 +355,7 @@ function buildVoiceToStaffMapForPart(part) {
355
355
  for (const measure of part.measures) {
356
356
  for (const entry of measure.entries) {
357
357
  if (entry.type === "note" && entry.staff !== void 0) {
358
- const voice = _nullishCoalesce(entry.voice, () => ( 1));
358
+ const voice = _nullishCoalesce(entry.voice, () => ( "1"));
359
359
  const staff = entry.staff;
360
360
  if (!map.has(voice)) {
361
361
  map.set(voice, staff);
@@ -374,7 +374,7 @@ function inferStaff(entry, voiceToStaffMap) {
374
374
  if (entry.staff !== void 0) {
375
375
  return entry.staff;
376
376
  }
377
- const inferredStaff = voiceToStaffMap.get(_nullishCoalesce(entry.voice, () => ( 1)));
377
+ const inferredStaff = voiceToStaffMap.get(_nullishCoalesce(entry.voice, () => ( "1")));
378
378
  if (inferredStaff !== void 0) {
379
379
  return inferredStaff;
380
380
  }
@@ -417,11 +417,11 @@ function getVoicesForStaff(measure, staff) {
417
417
  if (entry.type === "note") {
418
418
  const entryStaff = _nullishCoalesce(entry.staff, () => ( 1));
419
419
  if (entryStaff === staff) {
420
- voices.add(_nullishCoalesce(entry.voice, () => ( 1)));
420
+ voices.add(_nullishCoalesce(entry.voice, () => ( "1")));
421
421
  }
422
422
  }
423
423
  }
424
- return Array.from(voices).sort((a, b) => a - b);
424
+ return Array.from(voices).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
425
425
  }
426
426
  function getStaffRange(score, partIndex) {
427
427
  const part = score.parts[partIndex];
@@ -8,7 +8,7 @@ import {
8
8
  getMeasureEndPosition,
9
9
  pitchToSemitone,
10
10
  semitoneToKeyAwarePitch
11
- } from "./chunk-ZDAN74FN.mjs";
11
+ } from "./chunk-LYSKKBKA.mjs";
12
12
 
13
13
  // src/id.ts
14
14
  import { nanoid } from "nanoid";
@@ -176,7 +176,9 @@ function validateMeasureDuration(measure, divisions, time, location, tolerance =
176
176
  const expectedDuration = beats / time.beatType * 4 * divisions;
177
177
  const voiceDurations = calculateVoiceDurations(measure);
178
178
  for (const [voiceKey, actualDuration] of voiceDurations.entries()) {
179
- const [staff, voice] = voiceKey.split("-").map(Number);
179
+ const dashIndex = voiceKey.indexOf("-");
180
+ const staff = Number(voiceKey.slice(0, dashIndex));
181
+ const voice = voiceKey.slice(dashIndex + 1);
180
182
  const diff = actualDuration - expectedDuration;
181
183
  if (Math.abs(diff) > tolerance) {
182
184
  if (diff > 0) {
@@ -265,7 +267,7 @@ function validateMeasureFullness(measure, divisions, time, location) {
265
267
  currentPosition -= entry.duration;
266
268
  } else if (entry.type === "forward") {
267
269
  const staff = entry.staff ?? 1;
268
- const voice = entry.voice ?? 1;
270
+ const voice = entry.voice ?? "1";
269
271
  const key = `${staff}-${voice}`;
270
272
  if (!voiceCoverage.has(key)) {
271
273
  voiceCoverage.set(key, { segments: [] });
@@ -278,7 +280,9 @@ function validateMeasureFullness(measure, divisions, time, location) {
278
280
  }
279
281
  }
280
282
  for (const [voiceKey, { segments }] of voiceCoverage.entries()) {
281
- const [staff, voice] = voiceKey.split("-").map(Number);
283
+ const dashIndex = voiceKey.indexOf("-");
284
+ const staff = Number(voiceKey.slice(0, dashIndex));
285
+ const voice = voiceKey.slice(dashIndex + 1);
282
286
  const sorted = [...segments].sort((a, b) => a.start - b.start);
283
287
  let lastEnd = 0;
284
288
  const gaps = [];
@@ -427,7 +431,9 @@ function validateBeams(measure, location) {
427
431
  }
428
432
  }
429
433
  for (const [beamKey, { entryIndex: startIndex, staff }] of openBeams.entries()) {
430
- const [beamNumber, voice] = beamKey.split("-").map(Number);
434
+ const beamDashIndex = beamKey.indexOf("-");
435
+ const beamNumber = Number(beamKey.slice(0, beamDashIndex));
436
+ const voice = beamKey.slice(beamDashIndex + 1);
431
437
  errors.push({
432
438
  code: "BEAM_BEGIN_WITHOUT_END",
433
439
  level: "error",
@@ -499,7 +505,11 @@ function validateTuplets(measure, location) {
499
505
  }
500
506
  }
501
507
  for (const [tupletKey, startIndex] of openTuplets.entries()) {
502
- const [tupletNumber, voice, staff] = tupletKey.split("-").map(Number);
508
+ const firstDash = tupletKey.indexOf("-");
509
+ const lastDash = tupletKey.lastIndexOf("-");
510
+ const tupletNumber = Number(tupletKey.slice(0, firstDash));
511
+ const voice = tupletKey.slice(firstDash + 1, lastDash);
512
+ const staff = Number(tupletKey.slice(lastDash + 1));
503
513
  errors.push({
504
514
  code: "TUPLET_START_WITHOUT_STOP",
505
515
  level: "error",
@@ -550,11 +560,11 @@ function validateVoiceStaff(measure, staves, location) {
550
560
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
551
561
  const entry = measure.entries[entryIndex];
552
562
  if (entry.type !== "note") continue;
553
- if (entry.voice !== void 0 && entry.voice <= 0) {
563
+ if (entry.voice !== void 0 && entry.voice.trim() === "") {
554
564
  errors.push({
555
565
  code: "INVALID_VOICE_NUMBER",
556
566
  level: "error",
557
- message: `Invalid voice number: ${entry.voice}. Must be positive.`,
567
+ message: `Invalid voice: empty string.`,
558
568
  location: { ...location, entryIndex, voice: entry.voice }
559
569
  });
560
570
  }
@@ -1025,7 +1035,11 @@ function validateSlursAcrossMeasures(part) {
1025
1035
  }
1026
1036
  }
1027
1037
  for (const [slurKey, { measureIndex, entryIndex }] of openSlurs.entries()) {
1028
- const [slurNumber, voice, staff] = slurKey.split("-").map(Number);
1038
+ const firstDash = slurKey.indexOf("-");
1039
+ const lastDash = slurKey.lastIndexOf("-");
1040
+ const slurNumber = Number(slurKey.slice(0, firstDash));
1041
+ const voice = slurKey.slice(firstDash + 1, lastDash);
1042
+ const staff = Number(slurKey.slice(lastDash + 1));
1029
1043
  const measure = part.measures[measureIndex];
1030
1044
  errors.push({
1031
1045
  code: "SLUR_START_WITHOUT_STOP",
@@ -1225,7 +1239,7 @@ function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, st
1225
1239
  _id: generateId(),
1226
1240
  type: "forward",
1227
1241
  duration: diff,
1228
- voice: entry.type === "note" ? entry.voice : 1,
1242
+ voice: entry.type === "note" ? entry.voice : "1",
1229
1243
  staff: entry.type === "note" ? entry.staff : void 0
1230
1244
  });
1231
1245
  currentPosition = targetPos;
@@ -2716,7 +2730,7 @@ function autoBeam(score, options) {
2716
2730
  for (const entry of measure.entries) {
2717
2731
  if (entry.type === "note") {
2718
2732
  if (!entry.chord && !entry.rest) {
2719
- const voice = entry.voice ?? 1;
2733
+ const voice = entry.voice ?? "1";
2720
2734
  if (options.voice === void 0 || voice === options.voice) {
2721
2735
  if (!notesByVoice.has(voice)) {
2722
2736
  notesByVoice.set(voice, []);
@@ -8,7 +8,7 @@
8
8
 
9
9
 
10
10
 
11
- var _chunkTIFUKSTHjs = require('./chunk-TIFUKSTH.js');
11
+ var _chunk24XBPMRJjs = require('./chunk-24XBPMRJ.js');
12
12
 
13
13
  // src/id.ts
14
14
  var _nanoid = require('nanoid');
@@ -176,7 +176,9 @@ function validateMeasureDuration(measure, divisions, time, location, tolerance =
176
176
  const expectedDuration = beats / time.beatType * 4 * divisions;
177
177
  const voiceDurations = calculateVoiceDurations(measure);
178
178
  for (const [voiceKey, actualDuration] of voiceDurations.entries()) {
179
- const [staff, voice] = voiceKey.split("-").map(Number);
179
+ const dashIndex = voiceKey.indexOf("-");
180
+ const staff = Number(voiceKey.slice(0, dashIndex));
181
+ const voice = voiceKey.slice(dashIndex + 1);
180
182
  const diff = actualDuration - expectedDuration;
181
183
  if (Math.abs(diff) > tolerance) {
182
184
  if (diff > 0) {
@@ -265,7 +267,7 @@ function validateMeasureFullness(measure, divisions, time, location) {
265
267
  currentPosition -= entry.duration;
266
268
  } else if (entry.type === "forward") {
267
269
  const staff = _nullishCoalesce(entry.staff, () => ( 1));
268
- const voice = _nullishCoalesce(entry.voice, () => ( 1));
270
+ const voice = _nullishCoalesce(entry.voice, () => ( "1"));
269
271
  const key = `${staff}-${voice}`;
270
272
  if (!voiceCoverage.has(key)) {
271
273
  voiceCoverage.set(key, { segments: [] });
@@ -278,7 +280,9 @@ function validateMeasureFullness(measure, divisions, time, location) {
278
280
  }
279
281
  }
280
282
  for (const [voiceKey, { segments }] of voiceCoverage.entries()) {
281
- const [staff, voice] = voiceKey.split("-").map(Number);
283
+ const dashIndex = voiceKey.indexOf("-");
284
+ const staff = Number(voiceKey.slice(0, dashIndex));
285
+ const voice = voiceKey.slice(dashIndex + 1);
282
286
  const sorted = [...segments].sort((a, b) => a.start - b.start);
283
287
  let lastEnd = 0;
284
288
  const gaps = [];
@@ -427,7 +431,9 @@ function validateBeams(measure, location) {
427
431
  }
428
432
  }
429
433
  for (const [beamKey, { entryIndex: startIndex, staff }] of openBeams.entries()) {
430
- const [beamNumber, voice] = beamKey.split("-").map(Number);
434
+ const beamDashIndex = beamKey.indexOf("-");
435
+ const beamNumber = Number(beamKey.slice(0, beamDashIndex));
436
+ const voice = beamKey.slice(beamDashIndex + 1);
431
437
  errors.push({
432
438
  code: "BEAM_BEGIN_WITHOUT_END",
433
439
  level: "error",
@@ -499,7 +505,11 @@ function validateTuplets(measure, location) {
499
505
  }
500
506
  }
501
507
  for (const [tupletKey, startIndex] of openTuplets.entries()) {
502
- const [tupletNumber, voice, staff] = tupletKey.split("-").map(Number);
508
+ const firstDash = tupletKey.indexOf("-");
509
+ const lastDash = tupletKey.lastIndexOf("-");
510
+ const tupletNumber = Number(tupletKey.slice(0, firstDash));
511
+ const voice = tupletKey.slice(firstDash + 1, lastDash);
512
+ const staff = Number(tupletKey.slice(lastDash + 1));
503
513
  errors.push({
504
514
  code: "TUPLET_START_WITHOUT_STOP",
505
515
  level: "error",
@@ -550,11 +560,11 @@ function validateVoiceStaff(measure, staves, location) {
550
560
  for (let entryIndex = 0; entryIndex < measure.entries.length; entryIndex++) {
551
561
  const entry = measure.entries[entryIndex];
552
562
  if (entry.type !== "note") continue;
553
- if (entry.voice !== void 0 && entry.voice <= 0) {
563
+ if (entry.voice !== void 0 && entry.voice.trim() === "") {
554
564
  errors.push({
555
565
  code: "INVALID_VOICE_NUMBER",
556
566
  level: "error",
557
- message: `Invalid voice number: ${entry.voice}. Must be positive.`,
567
+ message: `Invalid voice: empty string.`,
558
568
  location: { ...location, entryIndex, voice: entry.voice }
559
569
  });
560
570
  }
@@ -1025,7 +1035,11 @@ function validateSlursAcrossMeasures(part) {
1025
1035
  }
1026
1036
  }
1027
1037
  for (const [slurKey, { measureIndex, entryIndex }] of openSlurs.entries()) {
1028
- const [slurNumber, voice, staff] = slurKey.split("-").map(Number);
1038
+ const firstDash = slurKey.indexOf("-");
1039
+ const lastDash = slurKey.lastIndexOf("-");
1040
+ const slurNumber = Number(slurKey.slice(0, firstDash));
1041
+ const voice = slurKey.slice(firstDash + 1, lastDash);
1042
+ const staff = Number(slurKey.slice(lastDash + 1));
1029
1043
  const measure = part.measures[measureIndex];
1030
1044
  errors.push({
1031
1045
  code: "SLUR_START_WITHOUT_STOP",
@@ -1225,7 +1239,7 @@ function rebuildMeasureWithVoice(measure, voice, newEntries, measureDuration, st
1225
1239
  _id: generateId(),
1226
1240
  type: "forward",
1227
1241
  duration: diff,
1228
- voice: entry.type === "note" ? entry.voice : 1,
1242
+ voice: entry.type === "note" ? entry.voice : "1",
1229
1243
  staff: entry.type === "note" ? entry.staff : void 0
1230
1244
  });
1231
1245
  currentPosition = targetPos;
@@ -1546,18 +1560,18 @@ function setNotePitchBySemitone(score, options) {
1546
1560
  const result = cloneScore(score);
1547
1561
  const measure = result.parts[options.partIndex].measures[options.measureIndex];
1548
1562
  const measureNumber = _nullishCoalesce(measure.number, () => ( String(options.measureIndex + 1)));
1549
- const attrs = _chunkTIFUKSTHjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1563
+ const attrs = _chunk24XBPMRJjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1550
1564
  const keySignature = _nullishCoalesce(attrs.key, () => ( { fifths: 0 }));
1551
1565
  let noteCount = 0;
1552
1566
  for (const entry of measure.entries) {
1553
1567
  if (entry.type === "note" && !entry.rest) {
1554
1568
  if (noteCount === options.noteIndex) {
1555
- const notePosition = _chunkTIFUKSTHjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1556
- const accidentalsInMeasure = _chunkTIFUKSTHjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1557
- const newPitch = _chunkTIFUKSTHjs.semitoneToKeyAwarePitch.call(void 0, options.semitone, keySignature, {
1569
+ const notePosition = _chunk24XBPMRJjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1570
+ const accidentalsInMeasure = _chunk24XBPMRJjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1571
+ const newPitch = _chunk24XBPMRJjs.semitoneToKeyAwarePitch.call(void 0, options.semitone, keySignature, {
1558
1572
  preferSharp: options.preferSharp
1559
1573
  });
1560
- const accidental = _chunkTIFUKSTHjs.determineAccidental.call(void 0, newPitch, keySignature, accidentalsInMeasure);
1574
+ const accidental = _chunk24XBPMRJjs.determineAccidental.call(void 0, newPitch, keySignature, accidentalsInMeasure);
1561
1575
  entry.pitch = newPitch;
1562
1576
  if (accidental) {
1563
1577
  entry.accidental = { value: accidental };
@@ -1591,7 +1605,7 @@ function shiftNotePitch(score, options) {
1591
1605
  if (!entry.pitch) {
1592
1606
  return failure([operationError("NOTE_NOT_FOUND", "Note has no pitch", { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
1593
1607
  }
1594
- currentSemitone = _chunkTIFUKSTHjs.pitchToSemitone.call(void 0, entry.pitch);
1608
+ currentSemitone = _chunk24XBPMRJjs.pitchToSemitone.call(void 0, entry.pitch);
1595
1609
  break;
1596
1610
  }
1597
1611
  noteCount++;
@@ -1619,7 +1633,7 @@ function raiseAccidental(score, options) {
1619
1633
  const result = cloneScore(score);
1620
1634
  const measure = result.parts[options.partIndex].measures[options.measureIndex];
1621
1635
  const measureNumber = _nullishCoalesce(measure.number, () => ( String(options.measureIndex + 1)));
1622
- const attrs = _chunkTIFUKSTHjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1636
+ const attrs = _chunk24XBPMRJjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1623
1637
  const keySignature = _nullishCoalesce(attrs.key, () => ( { fifths: 0 }));
1624
1638
  let noteCount = 0;
1625
1639
  for (const entry of measure.entries) {
@@ -1634,9 +1648,9 @@ function raiseAccidental(score, options) {
1634
1648
  return failure([operationError("ACCIDENTAL_OUT_OF_BOUNDS", `Cannot raise accidental beyond double-sharp (current: ${currentAlter})`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
1635
1649
  }
1636
1650
  entry.pitch.alter = newAlter === 0 ? void 0 : newAlter;
1637
- const notePosition = _chunkTIFUKSTHjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1638
- const accidentalsInMeasure = _chunkTIFUKSTHjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1639
- const accidental = _chunkTIFUKSTHjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1651
+ const notePosition = _chunk24XBPMRJjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1652
+ const accidentalsInMeasure = _chunk24XBPMRJjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1653
+ const accidental = _chunk24XBPMRJjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1640
1654
  if (accidental) {
1641
1655
  entry.accidental = { value: accidental };
1642
1656
  } else {
@@ -1660,7 +1674,7 @@ function lowerAccidental(score, options) {
1660
1674
  const result = cloneScore(score);
1661
1675
  const measure = result.parts[options.partIndex].measures[options.measureIndex];
1662
1676
  const measureNumber = _nullishCoalesce(measure.number, () => ( String(options.measureIndex + 1)));
1663
- const attrs = _chunkTIFUKSTHjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1677
+ const attrs = _chunk24XBPMRJjs.getAttributesAtMeasure.call(void 0, result, { part: options.partIndex, measure: measureNumber });
1664
1678
  const keySignature = _nullishCoalesce(attrs.key, () => ( { fifths: 0 }));
1665
1679
  let noteCount = 0;
1666
1680
  for (const entry of measure.entries) {
@@ -1675,9 +1689,9 @@ function lowerAccidental(score, options) {
1675
1689
  return failure([operationError("ACCIDENTAL_OUT_OF_BOUNDS", `Cannot lower accidental beyond double-flat (current: ${currentAlter})`, { partIndex: options.partIndex, measureIndex: options.measureIndex })]);
1676
1690
  }
1677
1691
  entry.pitch.alter = newAlter === 0 ? void 0 : newAlter;
1678
- const notePosition = _chunkTIFUKSTHjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1679
- const accidentalsInMeasure = _chunkTIFUKSTHjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1680
- const accidental = _chunkTIFUKSTHjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1692
+ const notePosition = _chunk24XBPMRJjs.getAbsolutePositionForNote.call(void 0, entry, measure);
1693
+ const accidentalsInMeasure = _chunk24XBPMRJjs.getAccidentalsInMeasure.call(void 0, measure, notePosition, entry.voice);
1694
+ const accidental = _chunk24XBPMRJjs.determineAccidental.call(void 0, entry.pitch, keySignature, accidentalsInMeasure);
1681
1695
  if (accidental) {
1682
1696
  entry.accidental = { value: accidental };
1683
1697
  } else {
@@ -1711,7 +1725,7 @@ function addVoice(score, options) {
1711
1725
  const context = getMeasureContext(result, options.partIndex, options.measureIndex);
1712
1726
  const measureDuration = context.time ? getMeasureDuration(context.divisions, context.time) : context.divisions * 4;
1713
1727
  const rest = createRest(measureDuration, options.voice, options.staff);
1714
- const currentEnd = _chunkTIFUKSTHjs.getMeasureEndPosition.call(void 0, measure);
1728
+ const currentEnd = _chunk24XBPMRJjs.getMeasureEndPosition.call(void 0, measure);
1715
1729
  if (currentEnd > 0) {
1716
1730
  measure.entries.push({ _id: generateId(), type: "backup", duration: currentEnd });
1717
1731
  }
@@ -1719,14 +1733,14 @@ function addVoice(score, options) {
1719
1733
  return success(result);
1720
1734
  }
1721
1735
  function transposePitch(pitch, semitones) {
1722
- const currentSemitone = _chunkTIFUKSTHjs.STEP_SEMITONES[pitch.step] + (_nullishCoalesce(pitch.alter, () => ( 0))) + pitch.octave * 12;
1736
+ const currentSemitone = _chunk24XBPMRJjs.STEP_SEMITONES[pitch.step] + (_nullishCoalesce(pitch.alter, () => ( 0))) + pitch.octave * 12;
1723
1737
  const targetSemitone = currentSemitone + semitones;
1724
1738
  const targetOctave = Math.floor(targetSemitone / 12);
1725
1739
  const targetPitchClass = (targetSemitone % 12 + 12) % 12;
1726
1740
  let bestStep = "C";
1727
1741
  let bestAlter = 99;
1728
- for (const step of _chunkTIFUKSTHjs.STEPS) {
1729
- const stepSemitone = _chunkTIFUKSTHjs.STEP_SEMITONES[step];
1742
+ for (const step of _chunk24XBPMRJjs.STEPS) {
1743
+ const stepSemitone = _chunk24XBPMRJjs.STEP_SEMITONES[step];
1730
1744
  let diff = targetPitchClass - stepSemitone;
1731
1745
  if (diff > 6) diff -= 12;
1732
1746
  if (diff < -6) diff += 12;
@@ -2716,7 +2730,7 @@ function autoBeam(score, options) {
2716
2730
  for (const entry of measure.entries) {
2717
2731
  if (entry.type === "note") {
2718
2732
  if (!entry.chord && !entry.rest) {
2719
- const voice = _nullishCoalesce(entry.voice, () => ( 1));
2733
+ const voice = _nullishCoalesce(entry.voice, () => ( "1"));
2720
2734
  if (options.voice === void 0 || voice === options.voice) {
2721
2735
  if (!notesByVoice.has(voice)) {
2722
2736
  notesByVoice.set(voice, []);
@@ -3788,7 +3802,7 @@ function addDaCapo(score, options) {
3788
3802
  }
3789
3803
  const result = cloneScore(score);
3790
3804
  const measure = result.parts[partIndex].measures[measureIndex];
3791
- const attrs = _chunkTIFUKSTHjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3805
+ const attrs = _chunk24XBPMRJjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3792
3806
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3793
3807
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3794
3808
  const direction = {
@@ -3817,7 +3831,7 @@ function addDalSegno(score, options) {
3817
3831
  }
3818
3832
  const result = cloneScore(score);
3819
3833
  const measure = result.parts[partIndex].measures[measureIndex];
3820
- const attrs = _chunkTIFUKSTHjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3834
+ const attrs = _chunk24XBPMRJjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3821
3835
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3822
3836
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3823
3837
  const direction = {
@@ -3846,7 +3860,7 @@ function addFine(score, options) {
3846
3860
  }
3847
3861
  const result = cloneScore(score);
3848
3862
  const measure = result.parts[partIndex].measures[measureIndex];
3849
- const attrs = _chunkTIFUKSTHjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3863
+ const attrs = _chunk24XBPMRJjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3850
3864
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3851
3865
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3852
3866
  const direction = {
@@ -3875,7 +3889,7 @@ function addToCoda(score, options) {
3875
3889
  }
3876
3890
  const result = cloneScore(score);
3877
3891
  const measure = result.parts[partIndex].measures[measureIndex];
3878
- const attrs = _chunkTIFUKSTHjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3892
+ const attrs = _chunk24XBPMRJjs.getAttributesAtMeasure.call(void 0, result, { part: partIndex, measure: measureIndex });
3879
3893
  const measureDuration = getMeasureDuration(_nullishCoalesce(attrs.divisions, () => ( 1)), _nullishCoalesce(attrs.time, () => ( { beats: "4", beatType: 4 })));
3880
3894
  const insertPos = _nullishCoalesce(position, () => ( measureDuration));
3881
3895
  const direction = {
@@ -191,7 +191,7 @@ function groupByVoice(measure) {
191
191
  for (const entry of measure.entries) {
192
192
  if (entry.type !== "note") continue;
193
193
  const staff = entry.staff ?? 1;
194
- const voice = entry.voice ?? 1;
194
+ const voice = entry.voice ?? "1";
195
195
  const key = `${staff}-${voice}`;
196
196
  if (!groups.has(key)) {
197
197
  groups.set(key, { staff, voice, notes: [] });
@@ -200,7 +200,7 @@ function groupByVoice(measure) {
200
200
  }
201
201
  return Array.from(groups.values()).sort((a, b) => {
202
202
  if (a.staff !== b.staff) return a.staff - b.staff;
203
- return a.voice - b.voice;
203
+ return a.voice.localeCompare(b.voice, void 0, { numeric: true });
204
204
  });
205
205
  }
206
206
  function groupByStaff(measure) {
@@ -285,10 +285,10 @@ function getVoices(measure) {
285
285
  const voices = /* @__PURE__ */ new Set();
286
286
  for (const entry of measure.entries) {
287
287
  if (entry.type === "note") {
288
- voices.add(entry.voice ?? 1);
288
+ voices.add(entry.voice ?? "1");
289
289
  }
290
290
  }
291
- return Array.from(voices).sort((a, b) => a - b);
291
+ return Array.from(voices).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
292
292
  }
293
293
  function getStaves(measure) {
294
294
  const staves = /* @__PURE__ */ new Set();
@@ -336,7 +336,7 @@ function buildVoiceToStaffMap(measure) {
336
336
  const map = /* @__PURE__ */ new Map();
337
337
  for (const entry of measure.entries) {
338
338
  if (entry.type === "note" && entry.staff !== void 0) {
339
- const voice = entry.voice ?? 1;
339
+ const voice = entry.voice ?? "1";
340
340
  const staff = entry.staff;
341
341
  if (!map.has(voice)) {
342
342
  map.set(voice, staff);
@@ -355,7 +355,7 @@ function buildVoiceToStaffMapForPart(part) {
355
355
  for (const measure of part.measures) {
356
356
  for (const entry of measure.entries) {
357
357
  if (entry.type === "note" && entry.staff !== void 0) {
358
- const voice = entry.voice ?? 1;
358
+ const voice = entry.voice ?? "1";
359
359
  const staff = entry.staff;
360
360
  if (!map.has(voice)) {
361
361
  map.set(voice, staff);
@@ -374,7 +374,7 @@ function inferStaff(entry, voiceToStaffMap) {
374
374
  if (entry.staff !== void 0) {
375
375
  return entry.staff;
376
376
  }
377
- const inferredStaff = voiceToStaffMap.get(entry.voice ?? 1);
377
+ const inferredStaff = voiceToStaffMap.get(entry.voice ?? "1");
378
378
  if (inferredStaff !== void 0) {
379
379
  return inferredStaff;
380
380
  }
@@ -417,11 +417,11 @@ function getVoicesForStaff(measure, staff) {
417
417
  if (entry.type === "note") {
418
418
  const entryStaff = entry.staff ?? 1;
419
419
  if (entryStaff === staff) {
420
- voices.add(entry.voice ?? 1);
420
+ voices.add(entry.voice ?? "1");
421
421
  }
422
422
  }
423
423
  }
424
- return Array.from(voices).sort((a, b) => a - b);
424
+ return Array.from(voices).sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
425
425
  }
426
426
  function getStaffRange(score, partIndex) {
427
427
  const part = score.parts[partIndex];
@@ -1,4 +1,4 @@
1
- import { S as Score, M as Measure, n as TimeSignature, f as Part, P as Pitch, N as NoteEntry, K as KeySignature, C as Clef, ac as ArticulationType, m as DynamicsValue, ad as OrnamentType, i as NoteType } from './types-D0G3_ykl.js';
1
+ import { S as Score, M as Measure, n as TimeSignature, f as Part, P as Pitch, N as NoteEntry, K as KeySignature, C as Clef, ac as ArticulationType, m as DynamicsValue, ad as OrnamentType, i as NoteType } from './types-BFmNsRNw.js';
2
2
 
3
3
  type ValidationErrorCode = 'MISSING_DIVISIONS' | 'INVALID_DIVISIONS' | 'MEASURE_DURATION_MISMATCH' | 'MEASURE_DURATION_OVERFLOW' | 'MEASURE_DURATION_UNDERFLOW' | 'VOICE_INCOMPLETE' | 'VOICE_GAP' | 'NEGATIVE_POSITION' | 'BACKUP_EXCEEDS_POSITION' | 'TIE_START_WITHOUT_STOP' | 'TIE_STOP_WITHOUT_START' | 'TIE_PITCH_MISMATCH' | 'BEAM_BEGIN_WITHOUT_END' | 'BEAM_END_WITHOUT_BEGIN' | 'SLUR_START_WITHOUT_STOP' | 'SLUR_STOP_WITHOUT_START' | 'TUPLET_START_WITHOUT_STOP' | 'TUPLET_STOP_WITHOUT_START' | 'PART_ID_NOT_IN_PART_LIST' | 'PART_LIST_ID_NOT_IN_PARTS' | 'PART_MEASURE_COUNT_MISMATCH' | 'PART_MEASURE_NUMBER_MISMATCH' | 'PART_GROUP_START_WITHOUT_STOP' | 'PART_GROUP_STOP_WITHOUT_START' | 'DUPLICATE_PART_ID' | 'INVALID_VOICE_NUMBER' | 'INVALID_STAFF_NUMBER' | 'STAFF_EXCEEDS_STAVES' | 'MISSING_STAVES_DECLARATION' | 'STAVES_DECLARATION_MISMATCH' | 'MISSING_CLEF_FOR_STAFF' | 'CLEF_STAFF_EXCEEDS_STAVES' | 'INVALID_DURATION' | 'EMPTY_MEASURE';
4
4
  type ValidationLevel = 'error' | 'warning' | 'info';
@@ -8,7 +8,7 @@ interface ValidationLocation {
8
8
  measureIndex?: number;
9
9
  measureNumber?: string;
10
10
  entryIndex?: number;
11
- voice?: number;
11
+ voice?: string;
12
12
  staff?: number;
13
13
  }
14
14
  interface ValidationError {
@@ -197,7 +197,7 @@ type OperationErrorCode = 'NOTE_CONFLICT' | 'EXCEEDS_MEASURE' | 'INVALID_POSITIO
197
197
  interface InsertNoteOptions {
198
198
  partIndex: number;
199
199
  measureIndex: number;
200
- voice: number;
200
+ voice: string;
201
201
  staff?: number;
202
202
  position: number;
203
203
  pitch: Pitch;
@@ -310,7 +310,7 @@ declare function lowerAccidental(score: Score, options: LowerAccidentalOptions):
310
310
  interface AddVoiceOptions {
311
311
  partIndex: number;
312
312
  measureIndex: number;
313
- voice: number;
313
+ voice: string;
314
314
  staff?: number;
315
315
  }
316
316
  /**
@@ -480,7 +480,7 @@ declare const addNote: (score: Score, options: {
480
480
  partIndex: number;
481
481
  measureIndex: number;
482
482
  staff?: number;
483
- voice: number;
483
+ voice: string;
484
484
  position: number;
485
485
  note: Omit<NoteEntry, "type" | "voice" | "staff">;
486
486
  }) => Score;
@@ -518,7 +518,7 @@ declare const addNoteChecked: (score: Score, options: {
518
518
  partIndex: number;
519
519
  measureIndex: number;
520
520
  staff?: number;
521
- voice: number;
521
+ voice: string;
522
522
  position: number;
523
523
  note: Omit<NoteEntry, "type" | "voice" | "staff">;
524
524
  }) => OperationResult<Score>;
@@ -618,7 +618,7 @@ interface AutoBeamOptions {
618
618
  partIndex: number;
619
619
  measureIndex: number;
620
620
  /** Optional voice filter */
621
- voice?: number;
621
+ voice?: string;
622
622
  /** Group by beat (default: true) */
623
623
  groupByBeat?: boolean;
624
624
  }
@@ -637,7 +637,7 @@ interface NoteSelection {
637
637
  measureIndex: number;
638
638
  startPosition: number;
639
639
  endPosition: number;
640
- voice: number;
640
+ voice: string;
641
641
  staff?: number;
642
642
  };
643
643
  /** Copied notes with their relative positions */
@@ -658,7 +658,7 @@ interface CopyNotesOptions {
658
658
  /** End position in the measure (in divisions) */
659
659
  endPosition: number;
660
660
  /** Voice to copy from */
661
- voice: number;
661
+ voice: string;
662
662
  /** Staff to copy from (optional) */
663
663
  staff?: number;
664
664
  }
@@ -677,7 +677,7 @@ interface PasteNotesOptions {
677
677
  /** Target position in the measure */
678
678
  position: number;
679
679
  /** Target voice (defaults to original voice) */
680
- voice?: number;
680
+ voice?: string;
681
681
  /** Target staff (defaults to original staff) */
682
682
  staff?: number;
683
683
  /** Clear existing notes in the paste range (default: true) */
@@ -704,7 +704,7 @@ interface CopyNotesMultiMeasureOptions {
704
704
  /** Ending measure index (inclusive) */
705
705
  endMeasureIndex: number;
706
706
  /** Voice to copy from */
707
- voice: number;
707
+ voice: string;
708
708
  /** Staff to copy from (optional) */
709
709
  staff?: number;
710
710
  }
@@ -716,7 +716,7 @@ interface MultiMeasureSelection {
716
716
  partIndex: number;
717
717
  startMeasureIndex: number;
718
718
  endMeasureIndex: number;
719
- voice: number;
719
+ voice: string;
720
720
  staff?: number;
721
721
  };
722
722
  /** Notes grouped by measure offset */
@@ -738,7 +738,7 @@ interface PasteNotesMultiMeasureOptions {
738
738
  /** Target starting measure index */
739
739
  startMeasureIndex: number;
740
740
  /** Target voice (defaults to original voice) */
741
- voice?: number;
741
+ voice?: string;
742
742
  /** Target staff (defaults to original staff) */
743
743
  staff?: number;
744
744
  /** Clear existing notes in paste measures (default: true) */
@@ -1038,7 +1038,7 @@ interface AddGraceNoteOptions {
1038
1038
  pitch: Pitch;
1039
1039
  noteType?: NoteType;
1040
1040
  slash?: boolean;
1041
- voice?: number;
1041
+ voice?: string;
1042
1042
  staff?: number;
1043
1043
  }
1044
1044
  /**