musicxml-io 0.1.0

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.
@@ -0,0 +1,315 @@
1
+ // src/utils/index.ts
2
+ var STEPS = ["C", "D", "E", "F", "G", "A", "B"];
3
+ var STEP_SEMITONES = {
4
+ "C": 0,
5
+ "D": 2,
6
+ "E": 4,
7
+ "F": 5,
8
+ "G": 7,
9
+ "A": 9,
10
+ "B": 11
11
+ };
12
+ function createPositionState() {
13
+ return { position: 0, lastNonChordPosition: 0 };
14
+ }
15
+ function updatePositionForEntry(state, entry) {
16
+ const pos = state.position;
17
+ switch (entry.type) {
18
+ case "note": {
19
+ const note = entry;
20
+ if (!note.chord) {
21
+ state.lastNonChordPosition = state.position;
22
+ state.position += note.duration;
23
+ }
24
+ return note.chord ? state.lastNonChordPosition : pos;
25
+ }
26
+ case "backup":
27
+ state.position -= entry.duration;
28
+ state.lastNonChordPosition = state.position;
29
+ return pos;
30
+ case "forward":
31
+ state.position += entry.duration;
32
+ state.lastNonChordPosition = state.position;
33
+ return pos;
34
+ default:
35
+ return pos;
36
+ }
37
+ }
38
+ function getMeasureEndPosition(measure) {
39
+ const state = createPositionState();
40
+ for (const entry of measure.entries) updatePositionForEntry(state, entry);
41
+ return state.position;
42
+ }
43
+
44
+ // src/operations/index.ts
45
+ function cloneScore(score) {
46
+ return JSON.parse(JSON.stringify(score));
47
+ }
48
+ function transposePitch(pitch, semitones) {
49
+ const currentSemitone = STEP_SEMITONES[pitch.step] + (pitch.alter ?? 0) + pitch.octave * 12;
50
+ const targetSemitone = currentSemitone + semitones;
51
+ const targetOctave = Math.floor(targetSemitone / 12);
52
+ const targetPitchClass = (targetSemitone % 12 + 12) % 12;
53
+ let bestStep = "C";
54
+ let bestAlter = 99;
55
+ for (const step of STEPS) {
56
+ const stepSemitone = STEP_SEMITONES[step];
57
+ let diff = targetPitchClass - stepSemitone;
58
+ if (diff > 6) diff -= 12;
59
+ if (diff < -6) diff += 12;
60
+ if (diff >= -2 && diff <= 2) {
61
+ if (Math.abs(diff) < Math.abs(bestAlter)) {
62
+ bestStep = step;
63
+ bestAlter = diff;
64
+ }
65
+ }
66
+ }
67
+ return {
68
+ step: bestStep,
69
+ octave: targetOctave,
70
+ alter: bestAlter !== 0 ? bestAlter : void 0
71
+ };
72
+ }
73
+ function transpose(score, semitones) {
74
+ if (semitones === 0) return score;
75
+ const result = cloneScore(score);
76
+ for (const part of result.parts) {
77
+ for (const measure of part.measures) {
78
+ for (const entry of measure.entries) {
79
+ if (entry.type === "note" && entry.pitch) {
80
+ entry.pitch = transposePitch(entry.pitch, semitones);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ return result;
86
+ }
87
+ function addNote(score, options) {
88
+ const result = cloneScore(score);
89
+ const part = result.parts[options.partIndex];
90
+ if (!part) return result;
91
+ const measure = part.measures[options.measureIndex];
92
+ if (!measure) return result;
93
+ const newNote = {
94
+ type: "note",
95
+ voice: options.voice,
96
+ staff: options.staff,
97
+ ...options.note
98
+ };
99
+ const currentPosition = getMeasureEndPosition(measure);
100
+ const positionDiff = options.position - currentPosition;
101
+ if (positionDiff < 0) {
102
+ measure.entries.push({
103
+ type: "backup",
104
+ duration: -positionDiff
105
+ });
106
+ } else if (positionDiff > 0) {
107
+ measure.entries.push({
108
+ type: "forward",
109
+ duration: positionDiff,
110
+ voice: options.voice,
111
+ staff: options.staff
112
+ });
113
+ }
114
+ measure.entries.push(newNote);
115
+ return result;
116
+ }
117
+ function deleteNote(score, options) {
118
+ const result = cloneScore(score);
119
+ const part = result.parts[options.partIndex];
120
+ if (!part) return result;
121
+ const measure = part.measures[options.measureIndex];
122
+ if (!measure) return result;
123
+ let noteCount = 0;
124
+ let entryIndex = -1;
125
+ for (let i = 0; i < measure.entries.length; i++) {
126
+ if (measure.entries[i].type === "note") {
127
+ if (noteCount === options.noteIndex) {
128
+ entryIndex = i;
129
+ break;
130
+ }
131
+ noteCount++;
132
+ }
133
+ }
134
+ if (entryIndex !== -1) {
135
+ measure.entries.splice(entryIndex, 1);
136
+ }
137
+ return result;
138
+ }
139
+ function changeKey(score, key, options) {
140
+ const result = cloneScore(score);
141
+ const targetMeasure = String(options.fromMeasure);
142
+ for (const part of result.parts) {
143
+ for (const measure of part.measures) {
144
+ if (measure.number === targetMeasure) {
145
+ if (!measure.attributes) {
146
+ measure.attributes = {};
147
+ }
148
+ measure.attributes.key = key;
149
+ }
150
+ }
151
+ }
152
+ return result;
153
+ }
154
+ function changeTime(score, time, options) {
155
+ const result = cloneScore(score);
156
+ const targetMeasure = String(options.fromMeasure);
157
+ for (const part of result.parts) {
158
+ for (const measure of part.measures) {
159
+ if (measure.number === targetMeasure) {
160
+ if (!measure.attributes) {
161
+ measure.attributes = {};
162
+ }
163
+ measure.attributes.time = time;
164
+ }
165
+ }
166
+ }
167
+ return result;
168
+ }
169
+ function insertMeasure(score, options) {
170
+ const result = cloneScore(score);
171
+ const targetMeasure = String(options.afterMeasure);
172
+ for (const part of result.parts) {
173
+ const insertIndex = part.measures.findIndex((m) => m.number === targetMeasure);
174
+ if (insertIndex === -1) continue;
175
+ const numericPart = parseInt(targetMeasure, 10);
176
+ const newMeasureNumber = String(isNaN(numericPart) ? insertIndex + 2 : numericPart + 1);
177
+ const newMeasure = {
178
+ number: newMeasureNumber,
179
+ entries: []
180
+ };
181
+ if (options.copyAttributes) {
182
+ const sourceMeasure = part.measures[insertIndex];
183
+ if (sourceMeasure.attributes) {
184
+ newMeasure.attributes = { ...sourceMeasure.attributes };
185
+ }
186
+ }
187
+ part.measures.splice(insertIndex + 1, 0, newMeasure);
188
+ for (let i = insertIndex + 2; i < part.measures.length; i++) {
189
+ const currentNum = parseInt(part.measures[i].number, 10);
190
+ if (!isNaN(currentNum)) {
191
+ part.measures[i].number = String(currentNum + 1);
192
+ }
193
+ }
194
+ }
195
+ return result;
196
+ }
197
+ function deleteMeasure(score, measureNumber) {
198
+ const result = cloneScore(score);
199
+ const targetMeasure = String(measureNumber);
200
+ for (const part of result.parts) {
201
+ const deleteIndex = part.measures.findIndex((m) => m.number === targetMeasure);
202
+ if (deleteIndex === -1) continue;
203
+ part.measures.splice(deleteIndex, 1);
204
+ for (let i = deleteIndex; i < part.measures.length; i++) {
205
+ const currentNum = parseInt(part.measures[i].number, 10);
206
+ if (!isNaN(currentNum)) {
207
+ part.measures[i].number = String(currentNum - 1);
208
+ }
209
+ }
210
+ }
211
+ return result;
212
+ }
213
+ function setDivisions(score, options) {
214
+ const result = cloneScore(score);
215
+ const part = result.parts[options.partIndex];
216
+ if (!part) return result;
217
+ const measure = part.measures[options.measureIndex];
218
+ if (!measure) return result;
219
+ if (!measure.attributes) {
220
+ measure.attributes = {};
221
+ }
222
+ measure.attributes.divisions = options.divisions;
223
+ return result;
224
+ }
225
+ function addChordNote(score, options) {
226
+ const result = cloneScore(score);
227
+ const part = result.parts[options.partIndex];
228
+ if (!part) return result;
229
+ const measure = part.measures[options.measureIndex];
230
+ if (!measure) return result;
231
+ let noteCount = 0;
232
+ let entryIndex = -1;
233
+ let targetNote = null;
234
+ for (let i = 0; i < measure.entries.length; i++) {
235
+ const entry = measure.entries[i];
236
+ if (entry.type === "note") {
237
+ if (noteCount === options.afterNoteIndex) {
238
+ entryIndex = i;
239
+ targetNote = entry;
240
+ break;
241
+ }
242
+ noteCount++;
243
+ }
244
+ }
245
+ if (entryIndex !== -1 && targetNote) {
246
+ const chordNote = {
247
+ type: "note",
248
+ pitch: options.pitch,
249
+ duration: targetNote.duration,
250
+ voice: targetNote.voice,
251
+ staff: targetNote.staff,
252
+ chord: true,
253
+ noteType: targetNote.noteType,
254
+ dots: targetNote.dots
255
+ };
256
+ measure.entries.splice(entryIndex + 1, 0, chordNote);
257
+ }
258
+ return result;
259
+ }
260
+ function modifyNotePitch(score, options) {
261
+ const result = cloneScore(score);
262
+ const part = result.parts[options.partIndex];
263
+ if (!part) return result;
264
+ const measure = part.measures[options.measureIndex];
265
+ if (!measure) return result;
266
+ let noteCount = 0;
267
+ for (const entry of measure.entries) {
268
+ if (entry.type === "note") {
269
+ if (noteCount === options.noteIndex) {
270
+ entry.pitch = options.pitch;
271
+ break;
272
+ }
273
+ noteCount++;
274
+ }
275
+ }
276
+ return result;
277
+ }
278
+ function modifyNoteDuration(score, options) {
279
+ const result = cloneScore(score);
280
+ const part = result.parts[options.partIndex];
281
+ if (!part) return result;
282
+ const measure = part.measures[options.measureIndex];
283
+ if (!measure) return result;
284
+ let noteCount = 0;
285
+ for (const entry of measure.entries) {
286
+ if (entry.type === "note") {
287
+ if (noteCount === options.noteIndex) {
288
+ entry.duration = options.duration;
289
+ if (options.noteType !== void 0) {
290
+ entry.noteType = options.noteType;
291
+ }
292
+ if (options.dots !== void 0) {
293
+ entry.dots = options.dots;
294
+ }
295
+ break;
296
+ }
297
+ noteCount++;
298
+ }
299
+ }
300
+ return result;
301
+ }
302
+ export {
303
+ addChordNote,
304
+ addNote,
305
+ changeKey,
306
+ changeTime,
307
+ deleteMeasure,
308
+ deleteNote,
309
+ insertMeasure,
310
+ modifyNoteDuration,
311
+ modifyNotePitch,
312
+ setDivisions,
313
+ transpose
314
+ };
315
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/index.ts","../../src/operations/index.ts"],"sourcesContent":["import type { Pitch, Measure, MeasureEntry, NoteEntry } from '../types';\n\n// Pitch constants\nexport const STEPS: Pitch['step'][] = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];\nexport const STEP_SEMITONES: Record<Pitch['step'], number> = {\n 'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11,\n};\n\n/** Convert pitch to semitone value (MIDI-like) */\nexport function pitchToSemitone(pitch: Pitch): number {\n return pitch.octave * 12 + STEP_SEMITONES[pitch.step] + (pitch.alter ?? 0);\n}\n\n// Position tracking for measure iteration\nexport interface PositionState {\n position: number;\n lastNonChordPosition: number;\n}\n\nexport function createPositionState(): PositionState {\n return { position: 0, lastNonChordPosition: 0 };\n}\n\n/** Update position state for entry, returns position before update */\nexport function updatePositionForEntry(state: PositionState, entry: MeasureEntry): number {\n const pos = state.position;\n switch (entry.type) {\n case 'note': {\n const note = entry as NoteEntry;\n if (!note.chord) {\n state.lastNonChordPosition = state.position;\n state.position += note.duration;\n }\n return note.chord ? state.lastNonChordPosition : pos;\n }\n case 'backup':\n state.position -= entry.duration;\n state.lastNonChordPosition = state.position;\n return pos;\n case 'forward':\n state.position += entry.duration;\n state.lastNonChordPosition = state.position;\n return pos;\n default:\n return pos;\n }\n}\n\n/** Get absolute position of a note within a measure */\nexport function getAbsolutePositionForNote(note: NoteEntry, measure: Measure): number {\n const state = createPositionState();\n for (const entry of measure.entries) {\n if (entry === note) return entry.chord ? state.lastNonChordPosition : state.position;\n updatePositionForEntry(state, entry);\n }\n return state.position;\n}\n\n/** Get position at end of measure */\nexport function getMeasureEndPosition(measure: Measure): number {\n const state = createPositionState();\n for (const entry of measure.entries) updatePositionForEntry(state, entry);\n return state.position;\n}\n","import type {\n Score,\n Measure,\n NoteEntry,\n Pitch,\n KeySignature,\n TimeSignature,\n} from '../types';\nimport { STEPS, STEP_SEMITONES, getMeasureEndPosition } from '../utils';\n\n/**\n * Deep clone a score\n */\nfunction cloneScore(score: Score): Score {\n return JSON.parse(JSON.stringify(score));\n}\n\n/**\n * Transpose a pitch by a number of semitones\n */\nfunction transposePitch(pitch: Pitch, semitones: number): Pitch {\n const currentSemitone = STEP_SEMITONES[pitch.step] + (pitch.alter ?? 0) + pitch.octave * 12;\n const targetSemitone = currentSemitone + semitones;\n\n const targetOctave = Math.floor(targetSemitone / 12);\n const targetPitchClass = ((targetSemitone % 12) + 12) % 12;\n\n // Find the closest natural step with smallest alteration\n let bestStep: Pitch['step'] = 'C';\n let bestAlter = 99; // Start with large value so any real alter is smaller\n\n for (const step of STEPS) {\n const stepSemitone = STEP_SEMITONES[step];\n // Calculate the alteration needed to reach target from this step\n let diff = targetPitchClass - stepSemitone;\n\n // Normalize to range -6 to +6 for smallest alteration\n if (diff > 6) diff -= 12;\n if (diff < -6) diff += 12;\n\n // Only consider alterations within -2 to +2 (double flat to double sharp)\n if (diff >= -2 && diff <= 2) {\n if (Math.abs(diff) < Math.abs(bestAlter)) {\n bestStep = step;\n bestAlter = diff;\n }\n }\n }\n\n return {\n step: bestStep,\n octave: targetOctave,\n alter: bestAlter !== 0 ? bestAlter : undefined,\n };\n}\n\n/**\n * Transpose all notes in a score by a number of semitones\n */\nexport function transpose(score: Score, semitones: number): Score {\n if (semitones === 0) return score;\n\n const result = cloneScore(score);\n\n for (const part of result.parts) {\n for (const measure of part.measures) {\n for (const entry of measure.entries) {\n if (entry.type === 'note' && entry.pitch) {\n entry.pitch = transposePitch(entry.pitch, semitones);\n }\n }\n }\n }\n\n return result;\n}\n\n/**\n * Options for adding a note\n */\nexport interface AddNoteOptions {\n partIndex: number;\n measureIndex: number;\n staff?: number;\n voice: number;\n position: number;\n note: Omit<NoteEntry, 'type' | 'voice' | 'staff'>;\n}\n\n/**\n * Add a note to a measure\n * Automatically handles backup/forward insertion\n */\nexport function addNote(score: Score, options: AddNoteOptions): Score {\n const result = cloneScore(score);\n const part = result.parts[options.partIndex];\n if (!part) return result;\n\n const measure = part.measures[options.measureIndex];\n if (!measure) return result;\n\n const newNote: NoteEntry = {\n type: 'note',\n voice: options.voice,\n staff: options.staff,\n ...options.note,\n };\n\n // Find the current position at the end of the measure using shared utility\n const currentPosition = getMeasureEndPosition(measure);\n\n // Calculate backup/forward needed\n const positionDiff = options.position - currentPosition;\n\n if (positionDiff < 0) {\n // Need to backup\n measure.entries.push({\n type: 'backup',\n duration: -positionDiff,\n });\n } else if (positionDiff > 0) {\n // Need to forward\n measure.entries.push({\n type: 'forward',\n duration: positionDiff,\n voice: options.voice,\n staff: options.staff,\n });\n }\n\n measure.entries.push(newNote);\n\n return result;\n}\n\n/**\n * Delete a note from a measure\n */\nexport function deleteNote(score: Score, options: {\n partIndex: number;\n measureIndex: number;\n noteIndex: number;\n}): Score {\n const result = cloneScore(score);\n const part = result.parts[options.partIndex];\n if (!part) return result;\n\n const measure = part.measures[options.measureIndex];\n if (!measure) return result;\n\n // Find the note by counting only notes\n let noteCount = 0;\n let entryIndex = -1;\n\n for (let i = 0; i < measure.entries.length; i++) {\n if (measure.entries[i].type === 'note') {\n if (noteCount === options.noteIndex) {\n entryIndex = i;\n break;\n }\n noteCount++;\n }\n }\n\n if (entryIndex !== -1) {\n measure.entries.splice(entryIndex, 1);\n }\n\n return result;\n}\n\n/**\n * Change key signature from a specific measure\n */\nexport function changeKey(\n score: Score,\n key: KeySignature,\n options: { fromMeasure: string | number }\n): Score {\n const result = cloneScore(score);\n const targetMeasure = String(options.fromMeasure);\n\n for (const part of result.parts) {\n for (const measure of part.measures) {\n if (measure.number === targetMeasure) {\n if (!measure.attributes) {\n measure.attributes = {};\n }\n measure.attributes.key = key;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Change time signature from a specific measure\n */\nexport function changeTime(\n score: Score,\n time: TimeSignature,\n options: { fromMeasure: string | number }\n): Score {\n const result = cloneScore(score);\n const targetMeasure = String(options.fromMeasure);\n\n for (const part of result.parts) {\n for (const measure of part.measures) {\n if (measure.number === targetMeasure) {\n if (!measure.attributes) {\n measure.attributes = {};\n }\n measure.attributes.time = time;\n }\n }\n }\n\n return result;\n}\n\n/**\n * Insert a new measure after a specific measure number\n */\nexport function insertMeasure(\n score: Score,\n options: {\n afterMeasure: string | number;\n copyAttributes?: boolean;\n }\n): Score {\n const result = cloneScore(score);\n const targetMeasure = String(options.afterMeasure);\n\n for (const part of result.parts) {\n const insertIndex = part.measures.findIndex((m) => m.number === targetMeasure);\n if (insertIndex === -1) continue;\n\n // Parse target measure number and increment for new measure\n const numericPart = parseInt(targetMeasure, 10);\n const newMeasureNumber = String(isNaN(numericPart) ? insertIndex + 2 : numericPart + 1);\n\n // Create new empty measure\n const newMeasure: Measure = {\n number: newMeasureNumber,\n entries: [],\n };\n\n // Copy attributes if requested\n if (options.copyAttributes) {\n const sourceMeasure = part.measures[insertIndex];\n if (sourceMeasure.attributes) {\n newMeasure.attributes = { ...sourceMeasure.attributes };\n }\n }\n\n // Insert the new measure\n part.measures.splice(insertIndex + 1, 0, newMeasure);\n\n // Update measure numbers for subsequent measures\n for (let i = insertIndex + 2; i < part.measures.length; i++) {\n const currentNum = parseInt(part.measures[i].number, 10);\n if (!isNaN(currentNum)) {\n part.measures[i].number = String(currentNum + 1);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Delete a measure\n */\nexport function deleteMeasure(score: Score, measureNumber: string | number): Score {\n const result = cloneScore(score);\n const targetMeasure = String(measureNumber);\n\n for (const part of result.parts) {\n const deleteIndex = part.measures.findIndex((m) => m.number === targetMeasure);\n if (deleteIndex === -1) continue;\n\n // Remove the measure\n part.measures.splice(deleteIndex, 1);\n\n // Update measure numbers for subsequent measures\n for (let i = deleteIndex; i < part.measures.length; i++) {\n const currentNum = parseInt(part.measures[i].number, 10);\n if (!isNaN(currentNum)) {\n part.measures[i].number = String(currentNum - 1);\n }\n }\n }\n\n return result;\n}\n\n/**\n * Set divisions for a measure\n */\nexport function setDivisions(\n score: Score,\n options: {\n partIndex: number;\n measureIndex: number;\n divisions: number;\n }\n): Score {\n const result = cloneScore(score);\n const part = result.parts[options.partIndex];\n if (!part) return result;\n\n const measure = part.measures[options.measureIndex];\n if (!measure) return result;\n\n if (!measure.attributes) {\n measure.attributes = {};\n }\n measure.attributes.divisions = options.divisions;\n\n return result;\n}\n\n/**\n * Add a chord note (note that sounds simultaneously with the previous note)\n */\nexport function addChordNote(\n score: Score,\n options: {\n partIndex: number;\n measureIndex: number;\n afterNoteIndex: number;\n pitch: Pitch;\n }\n): Score {\n const result = cloneScore(score);\n const part = result.parts[options.partIndex];\n if (!part) return result;\n\n const measure = part.measures[options.measureIndex];\n if (!measure) return result;\n\n // Find the note by counting only notes\n let noteCount = 0;\n let entryIndex = -1;\n let targetNote: NoteEntry | null = null;\n\n for (let i = 0; i < measure.entries.length; i++) {\n const entry = measure.entries[i];\n if (entry.type === 'note') {\n if (noteCount === options.afterNoteIndex) {\n entryIndex = i;\n targetNote = entry;\n break;\n }\n noteCount++;\n }\n }\n\n if (entryIndex !== -1 && targetNote) {\n const chordNote: NoteEntry = {\n type: 'note',\n pitch: options.pitch,\n duration: targetNote.duration,\n voice: targetNote.voice,\n staff: targetNote.staff,\n chord: true,\n noteType: targetNote.noteType,\n dots: targetNote.dots,\n };\n\n measure.entries.splice(entryIndex + 1, 0, chordNote);\n }\n\n return result;\n}\n\n/**\n * Modify a note's pitch\n */\nexport function modifyNotePitch(\n score: Score,\n options: {\n partIndex: number;\n measureIndex: number;\n noteIndex: number;\n pitch: Pitch;\n }\n): Score {\n const result = cloneScore(score);\n const part = result.parts[options.partIndex];\n if (!part) return result;\n\n const measure = part.measures[options.measureIndex];\n if (!measure) return result;\n\n // Find the note by counting only notes\n let noteCount = 0;\n\n for (const entry of measure.entries) {\n if (entry.type === 'note') {\n if (noteCount === options.noteIndex) {\n entry.pitch = options.pitch;\n break;\n }\n noteCount++;\n }\n }\n\n return result;\n}\n\n/**\n * Modify a note's duration\n */\nexport function modifyNoteDuration(\n score: Score,\n options: {\n partIndex: number;\n measureIndex: number;\n noteIndex: number;\n duration: number;\n noteType?: NoteEntry['noteType'];\n dots?: number;\n }\n): Score {\n const result = cloneScore(score);\n const part = result.parts[options.partIndex];\n if (!part) return result;\n\n const measure = part.measures[options.measureIndex];\n if (!measure) return result;\n\n // Find the note by counting only notes\n let noteCount = 0;\n\n for (const entry of measure.entries) {\n if (entry.type === 'note') {\n if (noteCount === options.noteIndex) {\n entry.duration = options.duration;\n if (options.noteType !== undefined) {\n entry.noteType = options.noteType;\n }\n if (options.dots !== undefined) {\n entry.dots = options.dots;\n }\n break;\n }\n noteCount++;\n }\n }\n\n return result;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,103 @@
1
+ import { S as Score, M as Measure, f as MeasureAttributes, a as Pitch, N as NoteEntry, P as Part } from '../types-DC_TnJu_.mjs';
2
+
3
+ /**
4
+ * Get a specific measure from the score
5
+ */
6
+ declare function getMeasure(score: Score, options: {
7
+ part: number;
8
+ measure: string | number;
9
+ }): Measure | undefined;
10
+ /**
11
+ * Get measure by index
12
+ */
13
+ declare function getMeasureByIndex(score: Score, options: {
14
+ part: number;
15
+ measureIndex: number;
16
+ }): Measure | undefined;
17
+ /**
18
+ * Get the total number of measures in a score
19
+ */
20
+ declare function getMeasureCount(score: Score): number;
21
+ /**
22
+ * Get the divisions value at a specific measure
23
+ * Searches backwards from the specified measure to find the most recent divisions
24
+ */
25
+ declare function getDivisions(score: Score, options: {
26
+ part: number;
27
+ measure: string | number;
28
+ }): number;
29
+ /**
30
+ * Get the current attributes at a specific measure
31
+ * Merges all attribute changes from measure 1 to the specified measure
32
+ */
33
+ declare function getAttributesAtMeasure(score: Score, options: {
34
+ part: number;
35
+ measure: string | number;
36
+ }): MeasureAttributes;
37
+ /**
38
+ * Pitch range filter
39
+ */
40
+ interface PitchRange {
41
+ min?: Pitch;
42
+ max?: Pitch;
43
+ }
44
+ /**
45
+ * Find notes filter
46
+ */
47
+ interface FindNotesFilter {
48
+ pitchRange?: PitchRange;
49
+ voice?: number;
50
+ staff?: number;
51
+ noteType?: string;
52
+ hasTie?: boolean;
53
+ }
54
+ /**
55
+ * Find notes matching specific criteria
56
+ */
57
+ declare function findNotes(score: Score, filter: FindNotesFilter): NoteEntry[];
58
+ /**
59
+ * Get the total duration of the score in divisions
60
+ */
61
+ declare function getDuration(score: Score): number;
62
+ /**
63
+ * Get part by ID
64
+ */
65
+ declare function getPartById(score: Score, id: string): Part | undefined;
66
+ /**
67
+ * Get part index by ID
68
+ */
69
+ declare function getPartIndex(score: Score, id: string): number;
70
+ /**
71
+ * Check if the score has multiple staves (e.g., piano grand staff)
72
+ */
73
+ declare function hasMultipleStaves(score: Score, partIndex?: number): boolean;
74
+ /**
75
+ * Get the number of staves for a part
76
+ */
77
+ declare function getStaveCount(score: Score, partIndex?: number): number;
78
+ /**
79
+ * Round-trip metrics for measuring preservation fidelity
80
+ */
81
+ interface RoundtripMetrics {
82
+ notesOriginal: number;
83
+ notesPreserved: number;
84
+ measuresOriginal: number;
85
+ measuresPreserved: number;
86
+ partsOriginal: number;
87
+ partsPreserved: number;
88
+ preservationRate: number;
89
+ }
90
+ /**
91
+ * Compare two scores and calculate round-trip preservation metrics
92
+ */
93
+ declare function measureRoundtrip(original: Score, exported: Score): RoundtripMetrics;
94
+ /**
95
+ * Count total notes in a score
96
+ */
97
+ declare function countNotes(score: Score): number;
98
+ /**
99
+ * Compare two scores for structural equality
100
+ */
101
+ declare function scoresEqual(a: Score, b: Score): boolean;
102
+
103
+ export { type FindNotesFilter, type PitchRange, type RoundtripMetrics, countNotes, findNotes, getAttributesAtMeasure, getDivisions, getDuration, getMeasure, getMeasureByIndex, getMeasureCount, getPartById, getPartIndex, getStaveCount, hasMultipleStaves, measureRoundtrip, scoresEqual };
@@ -0,0 +1,103 @@
1
+ import { S as Score, M as Measure, f as MeasureAttributes, a as Pitch, N as NoteEntry, P as Part } from '../types-DC_TnJu_.js';
2
+
3
+ /**
4
+ * Get a specific measure from the score
5
+ */
6
+ declare function getMeasure(score: Score, options: {
7
+ part: number;
8
+ measure: string | number;
9
+ }): Measure | undefined;
10
+ /**
11
+ * Get measure by index
12
+ */
13
+ declare function getMeasureByIndex(score: Score, options: {
14
+ part: number;
15
+ measureIndex: number;
16
+ }): Measure | undefined;
17
+ /**
18
+ * Get the total number of measures in a score
19
+ */
20
+ declare function getMeasureCount(score: Score): number;
21
+ /**
22
+ * Get the divisions value at a specific measure
23
+ * Searches backwards from the specified measure to find the most recent divisions
24
+ */
25
+ declare function getDivisions(score: Score, options: {
26
+ part: number;
27
+ measure: string | number;
28
+ }): number;
29
+ /**
30
+ * Get the current attributes at a specific measure
31
+ * Merges all attribute changes from measure 1 to the specified measure
32
+ */
33
+ declare function getAttributesAtMeasure(score: Score, options: {
34
+ part: number;
35
+ measure: string | number;
36
+ }): MeasureAttributes;
37
+ /**
38
+ * Pitch range filter
39
+ */
40
+ interface PitchRange {
41
+ min?: Pitch;
42
+ max?: Pitch;
43
+ }
44
+ /**
45
+ * Find notes filter
46
+ */
47
+ interface FindNotesFilter {
48
+ pitchRange?: PitchRange;
49
+ voice?: number;
50
+ staff?: number;
51
+ noteType?: string;
52
+ hasTie?: boolean;
53
+ }
54
+ /**
55
+ * Find notes matching specific criteria
56
+ */
57
+ declare function findNotes(score: Score, filter: FindNotesFilter): NoteEntry[];
58
+ /**
59
+ * Get the total duration of the score in divisions
60
+ */
61
+ declare function getDuration(score: Score): number;
62
+ /**
63
+ * Get part by ID
64
+ */
65
+ declare function getPartById(score: Score, id: string): Part | undefined;
66
+ /**
67
+ * Get part index by ID
68
+ */
69
+ declare function getPartIndex(score: Score, id: string): number;
70
+ /**
71
+ * Check if the score has multiple staves (e.g., piano grand staff)
72
+ */
73
+ declare function hasMultipleStaves(score: Score, partIndex?: number): boolean;
74
+ /**
75
+ * Get the number of staves for a part
76
+ */
77
+ declare function getStaveCount(score: Score, partIndex?: number): number;
78
+ /**
79
+ * Round-trip metrics for measuring preservation fidelity
80
+ */
81
+ interface RoundtripMetrics {
82
+ notesOriginal: number;
83
+ notesPreserved: number;
84
+ measuresOriginal: number;
85
+ measuresPreserved: number;
86
+ partsOriginal: number;
87
+ partsPreserved: number;
88
+ preservationRate: number;
89
+ }
90
+ /**
91
+ * Compare two scores and calculate round-trip preservation metrics
92
+ */
93
+ declare function measureRoundtrip(original: Score, exported: Score): RoundtripMetrics;
94
+ /**
95
+ * Count total notes in a score
96
+ */
97
+ declare function countNotes(score: Score): number;
98
+ /**
99
+ * Compare two scores for structural equality
100
+ */
101
+ declare function scoresEqual(a: Score, b: Score): boolean;
102
+
103
+ export { type FindNotesFilter, type PitchRange, type RoundtripMetrics, countNotes, findNotes, getAttributesAtMeasure, getDivisions, getDuration, getMeasure, getMeasureByIndex, getMeasureCount, getPartById, getPartIndex, getStaveCount, hasMultipleStaves, measureRoundtrip, scoresEqual };