@scorelabs/core 1.0.10 → 1.0.13

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.
Files changed (61) hide show
  1. package/dist/constants.d.ts +4 -0
  2. package/dist/constants.js +10 -0
  3. package/dist/importers/MusicXMLParser.d.ts +4 -1
  4. package/dist/importers/MusicXMLParser.js +564 -75
  5. package/dist/importers/index.d.ts +1 -1
  6. package/dist/importers/index.js +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.js +1 -0
  9. package/dist/models/Measure.d.ts +25 -5
  10. package/dist/models/Measure.js +173 -35
  11. package/dist/models/Note.d.ts +38 -9
  12. package/dist/models/Note.js +104 -47
  13. package/dist/models/NoteSet.d.ts +14 -4
  14. package/dist/models/NoteSet.js +38 -4
  15. package/dist/models/Part.d.ts +6 -6
  16. package/dist/models/Part.js +4 -4
  17. package/dist/models/Pitch.d.ts +1 -1
  18. package/dist/models/Pitch.js +1 -1
  19. package/dist/models/PreMeasure.d.ts +1 -1
  20. package/dist/models/Score.d.ts +13 -9
  21. package/dist/models/Score.js +164 -126
  22. package/dist/models/Staff.d.ts +5 -5
  23. package/dist/models/Staff.js +4 -4
  24. package/dist/models/index.d.ts +10 -10
  25. package/dist/models/index.js +10 -10
  26. package/dist/types/AccidentalDisplay.d.ts +6 -0
  27. package/dist/types/AccidentalDisplay.js +1 -0
  28. package/dist/types/Arpeggio.d.ts +2 -1
  29. package/dist/types/Arpeggio.js +1 -0
  30. package/dist/types/Articulation.d.ts +3 -1
  31. package/dist/types/Articulation.js +2 -0
  32. package/dist/types/Beam.d.ts +4 -0
  33. package/dist/types/Beam.js +1 -0
  34. package/dist/types/Duration.d.ts +5 -1
  35. package/dist/types/Duration.js +27 -14
  36. package/dist/types/Grace.d.ts +7 -0
  37. package/dist/types/Grace.js +1 -0
  38. package/dist/types/Hairpin.d.ts +2 -1
  39. package/dist/types/Language.d.ts +6 -0
  40. package/dist/types/Language.js +7 -0
  41. package/dist/types/Lyric.d.ts +2 -0
  42. package/dist/types/Ornament.d.ts +23 -1
  43. package/dist/types/Ornament.js +9 -0
  44. package/dist/types/Pedal.d.ts +3 -2
  45. package/dist/types/Placement.d.ts +7 -0
  46. package/dist/types/Placement.js +1 -0
  47. package/dist/types/Repeat.d.ts +1 -0
  48. package/dist/types/RestDisplay.d.ts +6 -0
  49. package/dist/types/RestDisplay.js +1 -0
  50. package/dist/types/Slur.d.ts +1 -0
  51. package/dist/types/Technical.d.ts +20 -0
  52. package/dist/types/Technical.js +5 -0
  53. package/dist/types/Tempo.d.ts +1 -1
  54. package/dist/types/Tie.d.ts +4 -0
  55. package/dist/types/Tie.js +1 -0
  56. package/dist/types/Tuplet.d.ts +6 -0
  57. package/dist/types/User.d.ts +13 -7
  58. package/dist/types/User.js +9 -7
  59. package/dist/types/index.d.ts +8 -0
  60. package/dist/types/index.js +8 -0
  61. package/package.json +2 -1
@@ -1 +1 @@
1
- export * from './MusicXMLParser';
1
+ export * from './MusicXMLParser.js';
@@ -1 +1 @@
1
- export * from './MusicXMLParser';
1
+ export * from './MusicXMLParser.js';
package/dist/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './models/index.js';
2
2
  export * from './importers/index.js';
3
+ export * from './constants.js';
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './models/index.js';
2
2
  export * from './importers/index.js';
3
+ export * from './constants.js';
@@ -1,6 +1,6 @@
1
- import { Note, NoteJSON } from './Note';
2
- import { NoteSet, NoteSetJSON } from './NoteSet';
3
- import { BarlineStyle, Clef, Duration, KeySignature, Repeat, Tempo, TimeSignature, Volta } from './types';
1
+ import { Note, NoteJSON } from './Note.js';
2
+ import { NoteSet, NoteSetJSON } from './NoteSet.js';
3
+ import { BarlineStyle, Clef, Duration, KeySignature, Repeat, Tempo, TimeSignature, Volta } from './types.js';
4
4
  /**
5
5
  * Represents a single measure containing note sets, potentially in multiple voices.
6
6
  */
@@ -17,12 +17,22 @@ export declare class Measure {
17
17
  readonly tempo?: Tempo | undefined;
18
18
  readonly rehearsalMark?: string | undefined;
19
19
  readonly systemText?: string | undefined;
20
+ readonly multiMeasureRestCount?: number | undefined;
20
21
  readonly barlineStyle: BarlineStyle;
21
- constructor(voices: NoteSet[][], timeSignature?: TimeSignature | undefined, keySignature?: KeySignature | undefined, systemBreak?: boolean, pageBreak?: boolean, repeats?: Repeat[], volta?: Volta | undefined, isPickup?: boolean, clef?: Clef | undefined, tempo?: Tempo | undefined, rehearsalMark?: string | undefined, systemText?: string | undefined, barlineStyle?: BarlineStyle);
22
+ readonly roadmapDirections?: string[] | undefined;
23
+ readonly systemDistance?: number | undefined;
24
+ readonly staffDistance?: number | undefined;
25
+ readonly topSystemDistance?: number | undefined;
26
+ /** Stable identifier for this measure. Preserved across all immutable updates. */
27
+ readonly uuid: string;
28
+ constructor(voices: NoteSet[][], timeSignature?: TimeSignature | undefined, keySignature?: KeySignature | undefined, systemBreak?: boolean, pageBreak?: boolean, repeats?: Repeat[], volta?: Volta | undefined, isPickup?: boolean, clef?: Clef | undefined, tempo?: Tempo | undefined, rehearsalMark?: string | undefined, systemText?: string | undefined, multiMeasureRestCount?: number | undefined, barlineStyle?: BarlineStyle, uuid?: string, roadmapDirections?: string[] | undefined, systemDistance?: number | undefined, staffDistance?: number | undefined, topSystemDistance?: number | undefined);
22
29
  get notes(): NoteSet[];
30
+ getNoteOffset(voiceIndex: number, noteIndex: number): number;
31
+ moveNoteToVoice(fromVoiceIndex: number, noteIndex: number, toVoiceIndex: number): Measure;
32
+ splitNote(voiceIndex: number, noteIndex: number, splitOffset: number): Measure;
23
33
  get hasStartRepeat(): boolean;
24
34
  get hasEndRepeat(): boolean;
25
- changeNoteDuration(noteIndex: number, newDuration: Duration, isDotted?: boolean, voiceIndex?: number): Measure;
35
+ changeNoteDuration(noteIndex: number, newDuration: Duration, dotCount?: number, voiceIndex?: number): Measure;
26
36
  getTotalDuration(voiceIndex?: number): number;
27
37
  static fromJSON(data: MeasureJSON): Measure;
28
38
  transpose(semitones: number): Measure;
@@ -45,12 +55,17 @@ export declare class Measure {
45
55
  withRehearsalMark(mark?: string): Measure;
46
56
  withSystemText(text?: string): Measure;
47
57
  withBarlineStyle(style: BarlineStyle): Measure;
58
+ withRoadmapDirections(roadmapDirections?: string[]): Measure;
59
+ withSystemDistance(systemDistance?: number): Measure;
60
+ withStaffDistance(staffDistance?: number): Measure;
61
+ withMultiMeasureRestCount(multiMeasureRestCount?: number): Measure;
48
62
  fillVoiceWithRests(voiceIndex: number, targetDuration: number): Measure;
49
63
  simplifyRests(voiceIndex?: number): Measure;
50
64
  private decomposeToRests;
51
65
  toJSON(): MeasureJSON;
52
66
  }
53
67
  export interface MeasureJSON {
68
+ uuid?: string;
54
69
  notes?: NoteJSON[];
55
70
  voices?: NoteSetJSON[][];
56
71
  timeSignature?: TimeSignature;
@@ -65,5 +80,10 @@ export interface MeasureJSON {
65
80
  tempo?: Tempo;
66
81
  rehearsalMark?: string;
67
82
  systemText?: string;
83
+ multiMeasureRestCount?: number;
84
+ roadmapDirections?: string[];
68
85
  barlineStyle?: BarlineStyle;
86
+ systemDistance?: number;
87
+ staffDistance?: number;
88
+ topSystemDistance?: number;
69
89
  }
@@ -1,6 +1,17 @@
1
- import { Note } from './Note';
2
- import { NoteSet } from './NoteSet';
3
- import { BarlineStyle, DURATION_VALUES, Duration, decomposeDuration, } from './types';
1
+ import { Note } from './Note.js';
2
+ import { NoteSet } from './NoteSet.js';
3
+ import { BarlineStyle, Duration, calculateDurationValue, decomposeDuration, } from './types.js';
4
+ /** Generate a v4-like UUID that works in both browser and Node environments. */
5
+ function generateUuid() {
6
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
7
+ return crypto.randomUUID();
8
+ }
9
+ // Fallback for older environments
10
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
11
+ const r = (Math.random() * 16) | 0;
12
+ return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
13
+ });
14
+ }
4
15
  /**
5
16
  * Represents a single measure containing note sets, potentially in multiple voices.
6
17
  */
@@ -17,8 +28,15 @@ export class Measure {
17
28
  tempo;
18
29
  rehearsalMark;
19
30
  systemText;
31
+ multiMeasureRestCount;
20
32
  barlineStyle;
21
- constructor(voices, timeSignature, keySignature, systemBreak = false, pageBreak = false, repeats = [], volta, isPickup = false, clef, tempo, rehearsalMark, systemText, barlineStyle = BarlineStyle.Regular) {
33
+ roadmapDirections;
34
+ systemDistance;
35
+ staffDistance;
36
+ topSystemDistance;
37
+ /** Stable identifier for this measure. Preserved across all immutable updates. */
38
+ uuid;
39
+ constructor(voices, timeSignature, keySignature, systemBreak = false, pageBreak = false, repeats = [], volta, isPickup = false, clef, tempo, rehearsalMark, systemText, multiMeasureRestCount, barlineStyle = BarlineStyle.Regular, uuid, roadmapDirections, systemDistance, staffDistance, topSystemDistance) {
22
40
  this.voices = voices;
23
41
  this.timeSignature = timeSignature;
24
42
  this.keySignature = keySignature;
@@ -31,7 +49,13 @@ export class Measure {
31
49
  this.tempo = tempo;
32
50
  this.rehearsalMark = rehearsalMark;
33
51
  this.systemText = systemText;
52
+ this.multiMeasureRestCount = multiMeasureRestCount;
34
53
  this.barlineStyle = barlineStyle;
54
+ this.roadmapDirections = roadmapDirections;
55
+ this.systemDistance = systemDistance;
56
+ this.staffDistance = staffDistance;
57
+ this.topSystemDistance = topSystemDistance;
58
+ this.uuid = uuid ?? generateUuid();
35
59
  if (this.voices.length === 0) {
36
60
  this.voices = [[]];
37
61
  }
@@ -39,27 +63,110 @@ export class Measure {
39
63
  get notes() {
40
64
  return this.voices[0] || [];
41
65
  }
66
+ getNoteOffset(voiceIndex, noteIndex) {
67
+ const voice = this.voices[voiceIndex];
68
+ if (!voice)
69
+ return 0;
70
+ let offset = 0;
71
+ for (let i = 0; i < noteIndex && i < voice.length; i++) {
72
+ offset += voice[i].getDurationValue();
73
+ }
74
+ return offset;
75
+ }
76
+ moveNoteToVoice(fromVoiceIndex, noteIndex, toVoiceIndex) {
77
+ if (fromVoiceIndex === toVoiceIndex)
78
+ return this;
79
+ const fromVoice = this.voices[fromVoiceIndex];
80
+ if (!fromVoice || noteIndex < 0 || noteIndex >= fromVoice.length)
81
+ return this;
82
+ const noteSetToMove = fromVoice[noteIndex];
83
+ if (noteSetToMove.isRest)
84
+ return this;
85
+ const onset = this.getNoteOffset(fromVoiceIndex, noteIndex);
86
+ // 1. Replace with rest in fromVoice first to get the source updated
87
+ let updatedMeasure = this.deleteNote(noteIndex, fromVoiceIndex);
88
+ // 2. Ensure toVoice exists and has rests up to total duration
89
+ const targetDur = this.timeSignature
90
+ ? (this.timeSignature.beats * 4) / this.timeSignature.beatType
91
+ : this.getTotalDuration(fromVoiceIndex);
92
+ updatedMeasure = updatedMeasure.fillVoiceWithRests(toVoiceIndex, targetDur);
93
+ // 3. Find the note/rest at 'onset' in toVoice and split if necessary
94
+ let toVoice = updatedMeasure.voices[toVoiceIndex];
95
+ let currentOffset = 0;
96
+ let targetNoteIndex = -1;
97
+ for (let i = 0; i < toVoice.length; i++) {
98
+ const dur = toVoice[i].getDurationValue();
99
+ if (Math.abs(currentOffset - onset) < 0.001) {
100
+ targetNoteIndex = i;
101
+ break;
102
+ }
103
+ if (onset > currentOffset + 0.001 && onset < currentOffset + dur - 0.001) {
104
+ // Need to split
105
+ updatedMeasure = updatedMeasure.splitNote(toVoiceIndex, i, onset - currentOffset);
106
+ toVoice = updatedMeasure.voices[toVoiceIndex]; // Update reference
107
+ // After split, the target should be at i + (number of segments added - 1)
108
+ // Let's just re-scan to be safe
109
+ currentOffset = 0;
110
+ for (let j = 0; j < toVoice.length; j++) {
111
+ if (Math.abs(currentOffset - onset) < 0.001) {
112
+ targetNoteIndex = j;
113
+ break;
114
+ }
115
+ currentOffset += toVoice[j].getDurationValue();
116
+ }
117
+ break;
118
+ }
119
+ currentOffset += dur;
120
+ }
121
+ if (targetNoteIndex !== -1) {
122
+ // We found the correct position in the prepared target voice
123
+ updatedMeasure = updatedMeasure.replaceNoteSet(targetNoteIndex, noteSetToMove, toVoiceIndex);
124
+ }
125
+ return updatedMeasure;
126
+ }
127
+ splitNote(voiceIndex, noteIndex, splitOffset) {
128
+ const voice = this.voices[voiceIndex];
129
+ if (!voice || noteIndex < 0 || noteIndex >= voice.length)
130
+ return this;
131
+ const target = voice[noteIndex];
132
+ const duration = target.getDurationValue();
133
+ if (splitOffset <= 0 || splitOffset >= duration - 0.001)
134
+ return this;
135
+ const leftParts = decomposeDuration(splitOffset);
136
+ const rightParts = decomposeDuration(duration - splitOffset);
137
+ const leftNotes = leftParts.map((p) => target.withDuration(p.duration, p.dotCount).withTie(target.isRest ? false : true));
138
+ const rightNotes = rightParts.map((p, idx) => {
139
+ const isLast = idx === rightParts.length - 1;
140
+ return target
141
+ .withDuration(p.duration, p.dotCount)
142
+ .withTie(isLast ? !!target.notes[0].tie : !target.isRest);
143
+ });
144
+ const newVoice = [...voice];
145
+ newVoice.splice(noteIndex, 1, ...leftNotes, ...rightNotes);
146
+ const newVoices = [...this.voices];
147
+ newVoices[voiceIndex] = newVoice;
148
+ return this.withVoices(newVoices);
149
+ }
42
150
  get hasStartRepeat() {
43
- return this.repeats.some((r) => r.type === "start");
151
+ return this.repeats.some((r) => r.type === 'start');
44
152
  }
45
153
  get hasEndRepeat() {
46
- return this.repeats.some((r) => r.type === "end");
154
+ return this.repeats.some((r) => r.type === 'end');
47
155
  }
48
- changeNoteDuration(noteIndex, newDuration, isDotted = false, voiceIndex = 0) {
156
+ changeNoteDuration(noteIndex, newDuration, dotCount = 0, voiceIndex = 0) {
49
157
  const voice = this.voices[voiceIndex];
50
158
  if (!voice || noteIndex < 0 || noteIndex >= voice.length)
51
159
  return this;
52
160
  const targetNoteSet = voice[noteIndex];
53
161
  const oldVal = targetNoteSet.getDurationValue();
54
- const base = DURATION_VALUES[newDuration];
55
- const newVal = isDotted ? base * 1.5 : base;
162
+ const newVal = calculateDurationValue(newDuration, dotCount);
56
163
  if (Math.abs(newVal - oldVal) < 0.001)
57
164
  return this;
58
165
  const updatedVoice = [...voice];
59
166
  if (newVal < oldVal) {
60
167
  const gap = oldVal - newVal;
61
- updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
62
- const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
168
+ updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, dotCount);
169
+ const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.dotCount)]));
63
170
  updatedVoice.splice(noteIndex + 1, 0, ...rests);
64
171
  }
65
172
  else {
@@ -70,7 +177,7 @@ export class Measure {
70
177
  }
71
178
  if (available < delta - 0.001)
72
179
  return this;
73
- updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
180
+ updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, dotCount);
74
181
  let consumed = 0;
75
182
  let removeCount = 0;
76
183
  const replacements = [];
@@ -85,7 +192,7 @@ export class Measure {
85
192
  }
86
193
  else {
87
194
  const remaining = consumed + val - delta;
88
- replacements.push(...decomposeDuration(remaining).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)])));
195
+ replacements.push(...decomposeDuration(remaining).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.dotCount)])));
89
196
  removeCount++;
90
197
  break;
91
198
  }
@@ -94,7 +201,12 @@ export class Measure {
94
201
  }
95
202
  const newVoices = [...this.voices];
96
203
  newVoices[voiceIndex] = updatedVoice;
97
- let result = this.withVoices(newVoices).simplifyRests(voiceIndex);
204
+ let result = this.withVoices(newVoices);
205
+ // Skip simplification if we are explicitly changing a rest's duration,
206
+ // to allow the user to manually control rest fragmentation (e.g. splitting a whole rest into halves).
207
+ if (!targetNoteSet.isRest) {
208
+ result = result.simplifyRests(voiceIndex);
209
+ }
98
210
  if (this.timeSignature) {
99
211
  result = result.autoBeam(this.timeSignature);
100
212
  }
@@ -123,7 +235,8 @@ export class Measure {
123
235
  else {
124
236
  voices = [[]];
125
237
  }
126
- return new Measure(voices, data.timeSignature, data.keySignature, data.systemBreak ?? false, data.pageBreak ?? false, data.repeats || (data.repeat ? [data.repeat] : []), data.volta, data.isPickup ?? false, data.clef, data.tempo, data.rehearsalMark, data.systemText, data.barlineStyle ?? BarlineStyle.Regular);
238
+ return new Measure(voices, data.timeSignature, data.keySignature, data.systemBreak ?? false, data.pageBreak ?? false, data.repeats || (data.repeat ? [data.repeat] : []), data.volta, data.isPickup ?? false, data.clef, data.tempo, data.rehearsalMark, data.systemText, data.multiMeasureRestCount, data.barlineStyle ?? BarlineStyle.Regular, data.uuid, // preserve existing uuid — undefined for old scores → generates new one
239
+ data.roadmapDirections, data.systemDistance, data.staffDistance, data.topSystemDistance);
127
240
  }
128
241
  transpose(semitones) {
129
242
  let newKeySignature;
@@ -164,7 +277,7 @@ export class Measure {
164
277
  for (let i = index + 1; i < updatedVoice.length; i++) {
165
278
  available += updatedVoice[i].getDurationValue();
166
279
  }
167
- // If there's enough space to consume, do it.
280
+ // If there's enough space to consume, do it.
168
281
  // If not, we still replace it but it will overflow (which simplifyRests might fix or layout will handle)
169
282
  updatedVoice[index] = newNoteSet;
170
283
  let consumed = 0;
@@ -190,7 +303,12 @@ export class Measure {
190
303
  }
191
304
  const newVoices = [...this.voices];
192
305
  newVoices[voiceIndex] = updatedVoice;
193
- let result = this.withVoices(newVoices).simplifyRests(voiceIndex);
306
+ let result = this.withVoices(newVoices);
307
+ // Only simplify rests if the duration was actually changed,
308
+ // otherwise preserve the user's current rhythm organization.
309
+ if (Math.abs(oldVal - newVal) >= 0.001) {
310
+ result = result.simplifyRests(voiceIndex);
311
+ }
194
312
  if (this.timeSignature) {
195
313
  result = result.autoBeam(this.timeSignature);
196
314
  }
@@ -235,7 +353,7 @@ export class Measure {
235
353
  if (ts.beats === 4 || ts.beats === 2) {
236
354
  // In 4/4 or 2/4, eighth notes are often beamed in groups of 2 beats (length 2.0)
237
355
  // unless there are sixteenth notes which force 1-beat groupings.
238
- beamBeatLength = (lastGroupHasSixteenths || isSixteenth) ? 1.0 : 2.0;
356
+ beamBeatLength = lastGroupHasSixteenths || isSixteenth ? 1.0 : 2.0;
239
357
  }
240
358
  }
241
359
  else if (ts.beatType === 8) {
@@ -291,25 +409,25 @@ export class Measure {
291
409
  return this.withVoices(newVoices);
292
410
  }
293
411
  withTimeSignature(timeSignature) {
294
- return new Measure(this.voices, timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
412
+ return new Measure(this.voices, timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
295
413
  }
296
414
  withKeySignature(keySignature) {
297
- return new Measure(this.voices, this.timeSignature, keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
415
+ return new Measure(this.voices, this.timeSignature, keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
298
416
  }
299
417
  withClef(clef) {
300
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
418
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
301
419
  }
302
420
  withTempo(tempo) {
303
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
421
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
304
422
  }
305
423
  withSystemBreak(systemBreak) {
306
- return new Measure(this.voices, this.timeSignature, this.keySignature, systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
424
+ return new Measure(this.voices, this.timeSignature, this.keySignature, systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
307
425
  }
308
426
  withPageBreak(pageBreak) {
309
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
427
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
310
428
  }
311
429
  withVoices(voices) {
312
- return new Measure(voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
430
+ return new Measure(voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
313
431
  }
314
432
  withVoiceNotes(notes, voiceIndex = 0) {
315
433
  const newVoices = [...this.voices];
@@ -332,22 +450,34 @@ export class Measure {
332
450
  return this.withRepeats(newRepeats);
333
451
  }
334
452
  withRepeats(repeats) {
335
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
453
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
336
454
  }
337
455
  withVolta(volta) {
338
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
456
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
339
457
  }
340
458
  withPickup(isPickup) {
341
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
459
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
342
460
  }
343
461
  withRehearsalMark(mark) {
344
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, mark, this.systemText, this.barlineStyle);
462
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, mark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
345
463
  }
346
464
  withSystemText(text) {
347
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, text, this.barlineStyle);
465
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, text, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
348
466
  }
349
467
  withBarlineStyle(style) {
350
- return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, style);
468
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, style, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
469
+ }
470
+ withRoadmapDirections(roadmapDirections) {
471
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, roadmapDirections, this.systemDistance, this.staffDistance);
472
+ }
473
+ withSystemDistance(systemDistance) {
474
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, systemDistance, this.staffDistance);
475
+ }
476
+ withStaffDistance(staffDistance) {
477
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, staffDistance);
478
+ }
479
+ withMultiMeasureRestCount(multiMeasureRestCount) {
480
+ return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, multiMeasureRestCount, this.barlineStyle, this.uuid, this.roadmapDirections, this.systemDistance, this.staffDistance);
351
481
  }
352
482
  fillVoiceWithRests(voiceIndex, targetDuration) {
353
483
  const newVoices = [...this.voices];
@@ -358,7 +488,7 @@ export class Measure {
358
488
  const currentDuration = currentVoice.reduce((sum, ns) => sum + ns.getDurationValue(), 0);
359
489
  if (currentDuration < targetDuration - 0.001) {
360
490
  const gap = targetDuration - currentDuration;
361
- const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
491
+ const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.dotCount)]));
362
492
  newVoices[voiceIndex] = [...currentVoice, ...rests];
363
493
  }
364
494
  return this.withVoices(newVoices);
@@ -368,9 +498,11 @@ export class Measure {
368
498
  if (!voice || voice.length === 0)
369
499
  return this;
370
500
  const totalDur = this.getTotalDuration(voiceIndex);
371
- const expectedDur = this.timeSignature ? (this.timeSignature.beats * 4) / this.timeSignature.beatType : 4.0;
501
+ const expectedDur = this.timeSignature
502
+ ? (this.timeSignature.beats * 4) / this.timeSignature.beatType
503
+ : 4.0;
372
504
  // Special Case: If the whole measure is rests, just return a single whole rest (or appropriate duration)
373
- const allRests = voice.every(ns => ns.isRest);
505
+ const allRests = voice.every((ns) => ns.isRest);
374
506
  if (allRests && Math.abs(totalDur - expectedDur) < 0.001) {
375
507
  const newVoice = [new NoteSet([new Note(Duration.Whole, undefined, true)])];
376
508
  const newVoices = [...this.voices];
@@ -399,10 +531,11 @@ export class Measure {
399
531
  return this.withVoices(newVoices);
400
532
  }
401
533
  decomposeToRests(value) {
402
- return decomposeDuration(value).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
534
+ return decomposeDuration(value).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.dotCount)]));
403
535
  }
404
536
  toJSON() {
405
537
  return {
538
+ uuid: this.uuid,
406
539
  voices: this.voices.map((v) => v.map((ns) => ns.toJSON())),
407
540
  timeSignature: this.timeSignature,
408
541
  keySignature: this.keySignature,
@@ -415,7 +548,12 @@ export class Measure {
415
548
  tempo: this.tempo,
416
549
  rehearsalMark: this.rehearsalMark,
417
550
  systemText: this.systemText,
551
+ multiMeasureRestCount: this.multiMeasureRestCount,
552
+ roadmapDirections: this.roadmapDirections,
418
553
  barlineStyle: this.barlineStyle,
554
+ systemDistance: this.systemDistance,
555
+ staffDistance: this.staffDistance,
556
+ topSystemDistance: this.topSystemDistance,
419
557
  };
420
558
  }
421
559
  }
@@ -1,13 +1,10 @@
1
- import { Pitch } from './Pitch';
2
- import { Accidental, Arpeggio, Articulation, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, Hairpin, Lyric, NoteheadShape, Ornament, Ottava, Pedal, Slur, StemDirection, Tuplet } from './types';
3
- /**
4
- * Represents a single note or rest in a measure.
5
- */
1
+ import { Pitch } from './Pitch.js';
2
+ import { Accidental, AccidentalDisplay, Arpeggio, Articulation, BeamLevels, Bend, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, GraceNote, Hairpin, Harmonic, Lyric, NoteheadShape, Ornament, OrnamentDetails, Ottava, Pedal, Placement, RestDisplay, Slur, StemDirection, TieSpan, Tuplet } from './types.js';
6
3
  export declare class Note {
7
4
  readonly duration: Duration;
8
5
  readonly pitch?: Pitch | undefined;
9
6
  readonly isRest: boolean;
10
- readonly isDotted: boolean;
7
+ readonly dotCount: number;
11
8
  readonly accidental?: Accidental | undefined;
12
9
  readonly beamGroup?: number | undefined;
13
10
  readonly articulations: Articulation[];
@@ -17,6 +14,7 @@ export declare class Note {
17
14
  readonly tuplet?: Tuplet | undefined;
18
15
  readonly hairpin?: Hairpin | undefined;
19
16
  readonly isGrace: boolean;
17
+ readonly isCue: boolean;
20
18
  readonly lyric?: Lyric | undefined;
21
19
  readonly chord?: string | undefined;
22
20
  readonly glissando?: Glissando | undefined;
@@ -38,9 +36,21 @@ export declare class Note {
38
36
  readonly palmMute?: "start" | "stop" | undefined;
39
37
  readonly hammerOn?: "start" | "stop" | undefined;
40
38
  readonly pullOff?: "start" | "stop" | undefined;
39
+ readonly tieSpans: TieSpan[];
40
+ readonly slurs: Slur[];
41
+ readonly beamLevels?: BeamLevels | undefined;
42
+ readonly grace?: GraceNote | undefined;
43
+ readonly accidentalDisplay?: AccidentalDisplay | undefined;
44
+ readonly restDisplay?: RestDisplay | undefined;
45
+ readonly ornamentDetails?: OrnamentDetails | undefined;
46
+ readonly harmonic?: Harmonic | undefined;
47
+ readonly bend?: Bend | undefined;
48
+ readonly vibrato?: "start" | "stop" | "single" | undefined;
49
+ readonly placement?: Placement | undefined;
41
50
  constructor(duration: Duration, pitch?: Pitch | undefined, // undefined for rests
42
- isRest?: boolean, isDotted?: boolean, accidental?: Accidental | undefined, beamGroup?: number | undefined, // Group ID for beamed notes
43
- articulations?: Articulation[], dynamic?: Dynamic | undefined, tie?: boolean | undefined, slur?: Slur | undefined, tuplet?: Tuplet | undefined, hairpin?: Hairpin | undefined, isGrace?: boolean, lyric?: Lyric | undefined, chord?: string | undefined, glissando?: Glissando | undefined, arpeggio?: Arpeggio | undefined, ottava?: Ottava | undefined, pedal?: Pedal | undefined, ornament?: Ornament | undefined, fret?: number | undefined, string?: number | undefined, fretboardDiagram?: FretboardDiagram | undefined, lyrics?: Lyric[] | undefined, staffText?: string | undefined, color?: string | undefined, notehead?: NoteheadShape | undefined, bowing?: Bowing | undefined, fingering?: number | undefined, stemDirection?: StemDirection | undefined, isStringCircled?: boolean | undefined, palmMute?: "start" | "stop" | undefined, hammerOn?: "start" | "stop" | undefined, pullOff?: "start" | "stop" | undefined);
51
+ isRest?: boolean, dotCount?: number, accidental?: Accidental | undefined, beamGroup?: number | undefined, // Group ID for beamed notes
52
+ articulations?: Articulation[], dynamic?: Dynamic | undefined, tie?: boolean | undefined, slur?: Slur | undefined, tuplet?: Tuplet | undefined, hairpin?: Hairpin | undefined, isGrace?: boolean, isCue?: boolean, lyric?: Lyric | undefined, chord?: string | undefined, glissando?: Glissando | undefined, arpeggio?: Arpeggio | undefined, ottava?: Ottava | undefined, pedal?: Pedal | undefined, ornament?: Ornament | undefined, fret?: number | undefined, string?: number | undefined, fretboardDiagram?: FretboardDiagram | undefined, lyrics?: Lyric[] | undefined, staffText?: string | undefined, color?: string | undefined, notehead?: NoteheadShape | undefined, bowing?: Bowing | undefined, fingering?: number | undefined, stemDirection?: StemDirection | undefined, isStringCircled?: boolean | undefined, palmMute?: "start" | "stop" | undefined, hammerOn?: "start" | "stop" | undefined, pullOff?: "start" | "stop" | undefined, tieSpans?: TieSpan[], slurs?: Slur[], beamLevels?: BeamLevels | undefined, grace?: GraceNote | undefined, accidentalDisplay?: AccidentalDisplay | undefined, restDisplay?: RestDisplay | undefined, ornamentDetails?: OrnamentDetails | undefined, harmonic?: Harmonic | undefined, bend?: Bend | undefined, vibrato?: "start" | "stop" | "single" | undefined, placement?: Placement | undefined);
53
+ get isDotted(): boolean;
44
54
  /**
45
55
  * Get all lyrics as standardized objects
46
56
  */
@@ -58,6 +68,7 @@ export declare class Note {
58
68
  */
59
69
  isBeamable(): boolean;
60
70
  withGrace(isGrace: boolean): Note;
71
+ withCue(isCue: boolean): Note;
61
72
  withChord(chord?: string): Note;
62
73
  transpose(semitones: number): Note;
63
74
  transposeOctave(octaves: number): Note;
@@ -74,7 +85,8 @@ export declare class Note {
74
85
  withLyrics(lyrics: Lyric[]): Note;
75
86
  withHairpin(hairpin?: Hairpin): Note;
76
87
  withAccidental(accidental?: Accidental): Note;
77
- withDuration(duration: Duration, isDotted?: boolean): Note;
88
+ withAccidentalDisplay(accidentalDisplay?: AccidentalDisplay): Note;
89
+ withDuration(duration: Duration, dotCount?: number): Note;
78
90
  withRest(isRest: boolean): Note;
79
91
  withFretboardDiagram(diagram?: FretboardDiagram): Note;
80
92
  withBeamGroup(beamGroup?: number): Note;
@@ -83,6 +95,7 @@ export declare class Note {
83
95
  withOttava(ottava?: Ottava): Note;
84
96
  withPedal(pedal?: Pedal): Note;
85
97
  withOrnament(ornament?: Ornament): Note;
98
+ withOrnamentDetails(ornamentDetails?: OrnamentDetails): Note;
86
99
  withTab(fret: number, string: number): Note;
87
100
  withStaffText(text?: string): Note;
88
101
  withColor(color?: string): Note;
@@ -94,6 +107,9 @@ export declare class Note {
94
107
  withPalmMute(palmMute?: 'start' | 'stop'): Note;
95
108
  withHammerOn(hammerOn?: 'start' | 'stop'): Note;
96
109
  withPullOff(pullOff?: 'start' | 'stop'): Note;
110
+ withHarmonic(harmonic?: Harmonic): Note;
111
+ withBend(bend?: Bend): Note;
112
+ withVibrato(vibrato?: 'start' | 'stop' | 'single'): Note;
97
113
  toJSON(): NoteJSON;
98
114
  static fromJSON(data: NoteJSON): Note;
99
115
  }
@@ -107,16 +123,21 @@ export interface NoteJSON {
107
123
  };
108
124
  isRest?: boolean;
109
125
  isDotted?: boolean;
126
+ dotCount?: number;
110
127
  accidental?: string;
111
128
  beamGroup?: number;
129
+ beamLevels?: BeamLevels;
112
130
  articulation?: string;
113
131
  articulations?: string[];
114
132
  dynamic?: string;
115
133
  tie?: boolean;
116
134
  slur?: Slur;
135
+ tieSpans?: TieSpan[];
136
+ slurs?: Slur[];
117
137
  tuplet?: Tuplet;
118
138
  hairpin?: Hairpin;
119
139
  isGrace?: boolean;
140
+ isCue?: boolean;
120
141
  lyric?: Lyric;
121
142
  chord?: string;
122
143
  glissando?: Glissando;
@@ -138,4 +159,12 @@ export interface NoteJSON {
138
159
  palmMute?: 'start' | 'stop';
139
160
  hammerOn?: 'start' | 'stop';
140
161
  pullOff?: 'start' | 'stop';
162
+ grace?: GraceNote;
163
+ accidentalDisplay?: AccidentalDisplay;
164
+ restDisplay?: RestDisplay;
165
+ ornamentDetails?: OrnamentDetails;
166
+ harmonic?: Harmonic;
167
+ bend?: Bend;
168
+ vibrato?: 'start' | 'stop' | 'single';
169
+ placement?: Placement;
141
170
  }