@scorelabs/core 1.0.9 → 1.0.11

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.
@@ -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
  */
@@ -20,16 +20,19 @@ export declare class Measure {
20
20
  readonly barlineStyle: BarlineStyle;
21
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
22
  get notes(): NoteSet[];
23
+ getNoteOffset(voiceIndex: number, noteIndex: number): number;
24
+ moveNoteToVoice(fromVoiceIndex: number, noteIndex: number, toVoiceIndex: number): Measure;
25
+ splitNote(voiceIndex: number, noteIndex: number, splitOffset: number): Measure;
23
26
  get hasStartRepeat(): boolean;
24
27
  get hasEndRepeat(): boolean;
25
- changeNoteDuration(noteIndex: number, newDuration: Duration, isDotted?: boolean, voiceIndex?: number): Measure;
28
+ changeNoteDuration(noteIndex: number, newDuration: Duration, dotCountOrIsDotted?: number | boolean, voiceIndex?: number): Measure;
26
29
  getTotalDuration(voiceIndex?: number): number;
27
30
  static fromJSON(data: MeasureJSON): Measure;
28
31
  transpose(semitones: number): Measure;
29
32
  replaceNoteSet(index: number, newNoteSet: NoteSet, voiceIndex?: number): Measure;
30
33
  replaceNote(noteIndex: number, newNote: Note, voiceIndex?: number): Measure;
31
34
  deleteNote(noteIndex: number, voiceIndex?: number): Measure;
32
- autoBeam(timeSign: TimeSignature): Measure;
35
+ autoBeam(timeSign?: TimeSignature): Measure;
33
36
  withTimeSignature(timeSignature?: TimeSignature): Measure;
34
37
  withKeySignature(keySignature?: KeySignature): Measure;
35
38
  withClef(clef?: Clef): Measure;
@@ -46,6 +49,8 @@ export declare class Measure {
46
49
  withSystemText(text?: string): Measure;
47
50
  withBarlineStyle(style: BarlineStyle): Measure;
48
51
  fillVoiceWithRests(voiceIndex: number, targetDuration: number): Measure;
52
+ simplifyRests(voiceIndex?: number): Measure;
53
+ private decomposeToRests;
49
54
  toJSON(): MeasureJSON;
50
55
  }
51
56
  export interface MeasureJSON {
@@ -1,6 +1,6 @@
1
- import { Note } from './Note';
2
- import { NoteSet } from './NoteSet';
3
- import { BarlineStyle, DURATION_VALUES, 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
4
  /**
5
5
  * Represents a single measure containing note sets, potentially in multiple voices.
6
6
  */
@@ -39,26 +39,110 @@ export class Measure {
39
39
  get notes() {
40
40
  return this.voices[0] || [];
41
41
  }
42
+ getNoteOffset(voiceIndex, noteIndex) {
43
+ const voice = this.voices[voiceIndex];
44
+ if (!voice)
45
+ return 0;
46
+ let offset = 0;
47
+ for (let i = 0; i < noteIndex && i < voice.length; i++) {
48
+ offset += voice[i].getDurationValue();
49
+ }
50
+ return offset;
51
+ }
52
+ moveNoteToVoice(fromVoiceIndex, noteIndex, toVoiceIndex) {
53
+ if (fromVoiceIndex === toVoiceIndex)
54
+ return this;
55
+ const fromVoice = this.voices[fromVoiceIndex];
56
+ if (!fromVoice || noteIndex < 0 || noteIndex >= fromVoice.length)
57
+ return this;
58
+ const noteSetToMove = fromVoice[noteIndex];
59
+ if (noteSetToMove.isRest)
60
+ return this;
61
+ const onset = this.getNoteOffset(fromVoiceIndex, noteIndex);
62
+ // 1. Replace with rest in fromVoice first to get the source updated
63
+ let updatedMeasure = this.deleteNote(noteIndex, fromVoiceIndex);
64
+ // 2. Ensure toVoice exists and has rests up to total duration
65
+ const targetDur = this.timeSignature
66
+ ? (this.timeSignature.beats * 4) / this.timeSignature.beatType
67
+ : this.getTotalDuration(fromVoiceIndex);
68
+ updatedMeasure = updatedMeasure.fillVoiceWithRests(toVoiceIndex, targetDur);
69
+ // 3. Find the note/rest at 'onset' in toVoice and split if necessary
70
+ let toVoice = updatedMeasure.voices[toVoiceIndex];
71
+ let currentOffset = 0;
72
+ let targetNoteIndex = -1;
73
+ for (let i = 0; i < toVoice.length; i++) {
74
+ const dur = toVoice[i].getDurationValue();
75
+ if (Math.abs(currentOffset - onset) < 0.001) {
76
+ targetNoteIndex = i;
77
+ break;
78
+ }
79
+ if (onset > currentOffset + 0.001 && onset < currentOffset + dur - 0.001) {
80
+ // Need to split
81
+ updatedMeasure = updatedMeasure.splitNote(toVoiceIndex, i, onset - currentOffset);
82
+ toVoice = updatedMeasure.voices[toVoiceIndex]; // Update reference
83
+ // After split, the target should be at i + (number of segments added - 1)
84
+ // Let's just re-scan to be safe
85
+ currentOffset = 0;
86
+ for (let j = 0; j < toVoice.length; j++) {
87
+ if (Math.abs(currentOffset - onset) < 0.001) {
88
+ targetNoteIndex = j;
89
+ break;
90
+ }
91
+ currentOffset += toVoice[j].getDurationValue();
92
+ }
93
+ break;
94
+ }
95
+ currentOffset += dur;
96
+ }
97
+ if (targetNoteIndex !== -1) {
98
+ // We found the correct position in the prepared target voice
99
+ updatedMeasure = updatedMeasure.replaceNoteSet(targetNoteIndex, noteSetToMove, toVoiceIndex);
100
+ }
101
+ return updatedMeasure;
102
+ }
103
+ splitNote(voiceIndex, noteIndex, splitOffset) {
104
+ const voice = this.voices[voiceIndex];
105
+ if (!voice || noteIndex < 0 || noteIndex >= voice.length)
106
+ return this;
107
+ const target = voice[noteIndex];
108
+ const duration = target.getDurationValue();
109
+ if (splitOffset <= 0 || splitOffset >= duration - 0.001)
110
+ return this;
111
+ const leftParts = decomposeDuration(splitOffset);
112
+ const rightParts = decomposeDuration(duration - splitOffset);
113
+ const leftNotes = leftParts.map((p) => target.withDuration(p.duration, p.isDotted).withTie(target.isRest ? false : true));
114
+ const rightNotes = rightParts.map((p, idx) => {
115
+ const isLast = idx === rightParts.length - 1;
116
+ return target
117
+ .withDuration(p.duration, p.isDotted)
118
+ .withTie(isLast ? !!target.notes[0].tie : !target.isRest);
119
+ });
120
+ const newVoice = [...voice];
121
+ newVoice.splice(noteIndex, 1, ...leftNotes, ...rightNotes);
122
+ const newVoices = [...this.voices];
123
+ newVoices[voiceIndex] = newVoice;
124
+ return this.withVoices(newVoices);
125
+ }
42
126
  get hasStartRepeat() {
43
- return this.repeats.some((r) => r.type === "start");
127
+ return this.repeats.some((r) => r.type === 'start');
44
128
  }
45
129
  get hasEndRepeat() {
46
- return this.repeats.some((r) => r.type === "end");
130
+ return this.repeats.some((r) => r.type === 'end');
47
131
  }
48
- changeNoteDuration(noteIndex, newDuration, isDotted = false, voiceIndex = 0) {
132
+ changeNoteDuration(noteIndex, newDuration, dotCountOrIsDotted = 0, voiceIndex = 0) {
49
133
  const voice = this.voices[voiceIndex];
50
134
  if (!voice || noteIndex < 0 || noteIndex >= voice.length)
51
135
  return this;
52
136
  const targetNoteSet = voice[noteIndex];
53
137
  const oldVal = targetNoteSet.getDurationValue();
54
- const base = DURATION_VALUES[newDuration];
55
- const newVal = isDotted ? base * 1.5 : base;
138
+ const dotCount = typeof dotCountOrIsDotted === 'number' ? dotCountOrIsDotted : (dotCountOrIsDotted ? 1 : 0);
139
+ const newVal = calculateDurationValue(newDuration, dotCount);
56
140
  if (Math.abs(newVal - oldVal) < 0.001)
57
141
  return this;
58
142
  const updatedVoice = [...voice];
59
143
  if (newVal < oldVal) {
60
144
  const gap = oldVal - newVal;
61
- updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
145
+ updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, dotCountOrIsDotted);
62
146
  const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
63
147
  updatedVoice.splice(noteIndex + 1, 0, ...rests);
64
148
  }
@@ -70,7 +154,7 @@ export class Measure {
70
154
  }
71
155
  if (available < delta - 0.001)
72
156
  return this;
73
- updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
157
+ updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, dotCountOrIsDotted);
74
158
  let consumed = 0;
75
159
  let removeCount = 0;
76
160
  const replacements = [];
@@ -94,7 +178,11 @@ export class Measure {
94
178
  }
95
179
  const newVoices = [...this.voices];
96
180
  newVoices[voiceIndex] = updatedVoice;
97
- return this.withVoices(newVoices);
181
+ let result = this.withVoices(newVoices).simplifyRests(voiceIndex);
182
+ if (this.timeSignature) {
183
+ result = result.autoBeam(this.timeSignature);
184
+ }
185
+ return result;
98
186
  }
99
187
  getTotalDuration(voiceIndex = 0) {
100
188
  const voice = this.voices[voiceIndex];
@@ -141,10 +229,56 @@ export class Measure {
141
229
  const voice = [...this.voices[voiceIndex]];
142
230
  if (index >= voice.length)
143
231
  return this;
144
- voice[index] = newNoteSet;
232
+ const oldVal = voice[index].getDurationValue();
233
+ const newVal = newNoteSet.getDurationValue();
234
+ // Use a copy for modifications
235
+ const updatedVoice = [...voice];
236
+ if (Math.abs(oldVal - newVal) < 0.001) {
237
+ updatedVoice[index] = newNoteSet;
238
+ }
239
+ else if (newVal < oldVal) {
240
+ const gap = oldVal - newVal;
241
+ updatedVoice[index] = newNoteSet;
242
+ const rests = this.decomposeToRests(gap);
243
+ updatedVoice.splice(index + 1, 0, ...rests);
244
+ }
245
+ else {
246
+ const delta = newVal - oldVal;
247
+ let available = 0;
248
+ for (let i = index + 1; i < updatedVoice.length; i++) {
249
+ available += updatedVoice[i].getDurationValue();
250
+ }
251
+ // If there's enough space to consume, do it.
252
+ // If not, we still replace it but it will overflow (which simplifyRests might fix or layout will handle)
253
+ updatedVoice[index] = newNoteSet;
254
+ let consumed = 0;
255
+ let removeCount = 0;
256
+ const replacements = [];
257
+ for (let i = index + 1; i < voice.length; i++) {
258
+ const ns = voice[i];
259
+ const val = ns.getDurationValue();
260
+ if (consumed + val <= delta + 0.001) {
261
+ consumed += val;
262
+ removeCount++;
263
+ if (consumed >= delta - 0.001)
264
+ break;
265
+ }
266
+ else {
267
+ const remaining = consumed + val - delta;
268
+ replacements.push(...this.decomposeToRests(remaining));
269
+ removeCount++;
270
+ break;
271
+ }
272
+ }
273
+ updatedVoice.splice(index + 1, removeCount, ...replacements);
274
+ }
145
275
  const newVoices = [...this.voices];
146
- newVoices[voiceIndex] = voice;
147
- return this.withVoices(newVoices);
276
+ newVoices[voiceIndex] = updatedVoice;
277
+ let result = this.withVoices(newVoices).simplifyRests(voiceIndex);
278
+ if (this.timeSignature) {
279
+ result = result.autoBeam(this.timeSignature);
280
+ }
281
+ return result;
148
282
  }
149
283
  replaceNote(noteIndex, newNote, voiceIndex = 0) {
150
284
  return this.replaceNoteSet(noteIndex, new NoteSet([newNote]), voiceIndex);
@@ -155,13 +289,20 @@ export class Measure {
155
289
  const voice = [...this.voices[voiceIndex]];
156
290
  if (noteIndex < 0 || noteIndex >= voice.length)
157
291
  return this;
158
- voice.splice(noteIndex, 1);
292
+ // Instead of splicing, replace with a rest of the same duration
293
+ const target = voice[noteIndex];
294
+ voice[noteIndex] = target.withRest(true);
159
295
  const newVoices = [...this.voices];
160
296
  newVoices[voiceIndex] = voice;
161
- return this.withVoices(newVoices);
297
+ return this.withVoices(newVoices).simplifyRests(voiceIndex);
162
298
  }
163
299
  autoBeam(timeSign) {
300
+ const ts = timeSign || this.timeSignature;
301
+ if (!ts)
302
+ return this;
164
303
  const newVoices = this.voices.map((voice) => {
304
+ if (voice.length === 0)
305
+ return [];
165
306
  let currentOffset = 0;
166
307
  let lastGroupId = undefined;
167
308
  let lastGroupStartTime = 0;
@@ -171,28 +312,38 @@ export class Measure {
171
312
  const noteDuration = ns.getDurationValue();
172
313
  let groupToAssign = undefined;
173
314
  if (ns.isBeamable()) {
174
- const isSixteenth = noteDuration < 0.5;
175
- let currentBeatLength = 4 / timeSign.beatType;
176
- if (timeSign.beatType === 8 && timeSign.beats % 3 === 0) {
177
- currentBeatLength = 1.5;
315
+ const isSixteenth = noteDuration < 0.5 - 0.001;
316
+ // Determine the logical "beat length" for beaming purposes
317
+ let beamBeatLength = 4 / ts.beatType; // Default is one beat
318
+ if (ts.beatType === 4) {
319
+ if (ts.beats === 4 || ts.beats === 2) {
320
+ // In 4/4 or 2/4, eighth notes are often beamed in groups of 2 beats (length 2.0)
321
+ // unless there are sixteenth notes which force 1-beat groupings.
322
+ beamBeatLength = lastGroupHasSixteenths || isSixteenth ? 1.0 : 2.0;
323
+ }
178
324
  }
179
- else if (timeSign.beatType === 4 && (timeSign.beats === 4 || timeSign.beats === 2)) {
180
- currentBeatLength = lastGroupHasSixteenths || isSixteenth ? 1.0 : 2.0;
325
+ else if (ts.beatType === 8) {
326
+ if (ts.beats % 3 === 0) {
327
+ // Compound meter (6/8, 9/8, 12/8): Beam by 3 eighth notes (1.5 length)
328
+ beamBeatLength = 1.5;
329
+ }
330
+ else {
331
+ // Simple meter in 8 (e.g. 2/8, 4/8): Beam by quarter note (1.0 length)
332
+ beamBeatLength = 1.0;
333
+ }
181
334
  }
182
- else if (timeSign.beatType === 2 && timeSign.beats === 2) {
183
- currentBeatLength = lastGroupHasSixteenths || isSixteenth ? 1.0 : 2.0;
184
- }
185
- const currentBeatId = Math.floor(currentOffset / currentBeatLength + 0.001);
335
+ const currentBeatId = Math.floor(currentOffset / beamBeatLength + 0.001);
186
336
  const endOffset = currentOffset + noteDuration;
187
- const crossesBeat = Math.floor(endOffset / currentBeatLength - 0.001) !== currentBeatId;
337
+ const crossesBeat = Math.floor(endOffset / beamBeatLength - 0.001) !== currentBeatId;
188
338
  const sameBeatAsLast = lastGroupId !== undefined &&
189
- Math.floor(lastGroupStartTime / currentBeatLength + 0.001) === currentBeatId;
339
+ Math.floor(lastGroupStartTime / beamBeatLength + 0.001) === currentBeatId;
190
340
  if (sameBeatAsLast && !crossesBeat) {
191
341
  groupToAssign = lastGroupId;
192
342
  if (isSixteenth)
193
343
  lastGroupHasSixteenths = true;
194
344
  }
195
345
  else {
346
+ // Start a new group
196
347
  lastGroupId = Math.floor(Math.random() * 1000000) + 1;
197
348
  lastGroupStartTime = currentOffset;
198
349
  lastGroupHasSixteenths = isSixteenth;
@@ -200,12 +351,14 @@ export class Measure {
200
351
  }
201
352
  }
202
353
  else {
354
+ // Non-beamable note (Quarter, Half, etc.) or Rest
203
355
  lastGroupId = undefined;
204
356
  lastGroupHasSixteenths = false;
205
357
  }
206
358
  tempNoteSets.push(ns.withBeamGroup(groupToAssign));
207
359
  currentOffset += noteDuration;
208
360
  }
361
+ // Cleanup: groups of 1 note are not beamed
209
362
  const groupCounts = new Map();
210
363
  for (const ns of tempNoteSets) {
211
364
  if (ns.beamGroup !== undefined) {
@@ -294,6 +447,46 @@ export class Measure {
294
447
  }
295
448
  return this.withVoices(newVoices);
296
449
  }
450
+ simplifyRests(voiceIndex = 0) {
451
+ const voice = this.voices[voiceIndex];
452
+ if (!voice || voice.length === 0)
453
+ return this;
454
+ const totalDur = this.getTotalDuration(voiceIndex);
455
+ const expectedDur = this.timeSignature
456
+ ? (this.timeSignature.beats * 4) / this.timeSignature.beatType
457
+ : 4.0;
458
+ // Special Case: If the whole measure is rests, just return a single whole rest (or appropriate duration)
459
+ const allRests = voice.every((ns) => ns.isRest);
460
+ if (allRests && Math.abs(totalDur - expectedDur) < 0.001) {
461
+ const newVoice = [new NoteSet([new Note(Duration.Whole, undefined, true)])];
462
+ const newVoices = [...this.voices];
463
+ newVoices[voiceIndex] = newVoice;
464
+ return this.withVoices(newVoices);
465
+ }
466
+ const newVoice = [];
467
+ let currentRestSum = 0;
468
+ for (const ns of voice) {
469
+ if (ns.isRest) {
470
+ currentRestSum += ns.getDurationValue();
471
+ }
472
+ else {
473
+ if (currentRestSum > 0.001) {
474
+ newVoice.push(...this.decomposeToRests(currentRestSum));
475
+ currentRestSum = 0;
476
+ }
477
+ newVoice.push(ns);
478
+ }
479
+ }
480
+ if (currentRestSum > 0.001) {
481
+ newVoice.push(...this.decomposeToRests(currentRestSum));
482
+ }
483
+ const newVoices = [...this.voices];
484
+ newVoices[voiceIndex] = newVoice;
485
+ return this.withVoices(newVoices);
486
+ }
487
+ decomposeToRests(value) {
488
+ return decomposeDuration(value).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
489
+ }
297
490
  toJSON() {
298
491
  return {
299
492
  voices: this.voices.map((v) => v.map((ns) => ns.toJSON())),
@@ -1,13 +1,9 @@
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, Arpeggio, Articulation, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, Hairpin, Lyric, NoteheadShape, Ornament, Ottava, Pedal, Slur, StemDirection, 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;
11
7
  readonly accidental?: Accidental | undefined;
12
8
  readonly beamGroup?: number | undefined;
13
9
  readonly articulations: Articulation[];
@@ -17,7 +13,7 @@ export declare class Note {
17
13
  readonly tuplet?: Tuplet | undefined;
18
14
  readonly hairpin?: Hairpin | undefined;
19
15
  readonly isGrace: boolean;
20
- readonly lyric?: string | undefined;
16
+ readonly lyric?: Lyric | undefined;
21
17
  readonly chord?: string | undefined;
22
18
  readonly glissando?: Glissando | undefined;
23
19
  readonly arpeggio?: Arpeggio | undefined;
@@ -27,7 +23,7 @@ export declare class Note {
27
23
  readonly fret?: number | undefined;
28
24
  readonly string?: number | undefined;
29
25
  readonly fretboardDiagram?: FretboardDiagram | undefined;
30
- readonly lyrics?: (string | Lyric)[] | undefined;
26
+ readonly lyrics?: Lyric[] | undefined;
31
27
  readonly staffText?: string | undefined;
32
28
  readonly color?: string | undefined;
33
29
  readonly notehead?: NoteheadShape | undefined;
@@ -39,8 +35,10 @@ export declare class Note {
39
35
  readonly hammerOn?: "start" | "stop" | undefined;
40
36
  readonly pullOff?: "start" | "stop" | undefined;
41
37
  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?: string | 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?: (string | 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);
38
+ isRest?: boolean, dotCountOrIsDotted?: number | boolean, accidental?: Accidental | undefined, beamGroup?: number | undefined, // Group ID for beamed notes
39
+ 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);
40
+ readonly dotCount: number;
41
+ get isDotted(): boolean;
44
42
  /**
45
43
  * Get all lyrics as standardized objects
46
44
  */
@@ -70,11 +68,11 @@ export declare class Note {
70
68
  withTie(tie?: boolean): Note;
71
69
  withSlur(slur?: Slur): Note;
72
70
  withTuplet(tuplet?: Tuplet): Note;
73
- withLyric(lyric?: string): Note;
74
- withLyrics(lyrics: (string | Lyric)[]): Note;
71
+ withLyric(lyric?: Lyric): Note;
72
+ withLyrics(lyrics: Lyric[]): Note;
75
73
  withHairpin(hairpin?: Hairpin): Note;
76
74
  withAccidental(accidental?: Accidental): Note;
77
- withDuration(duration: Duration, isDotted?: boolean): Note;
75
+ withDuration(duration: Duration, dotCountOrIsDotted?: number | boolean): Note;
78
76
  withRest(isRest: boolean): Note;
79
77
  withFretboardDiagram(diagram?: FretboardDiagram): Note;
80
78
  withBeamGroup(beamGroup?: number): Note;
@@ -107,6 +105,7 @@ export interface NoteJSON {
107
105
  };
108
106
  isRest?: boolean;
109
107
  isDotted?: boolean;
108
+ dotCount?: number;
110
109
  accidental?: string;
111
110
  beamGroup?: number;
112
111
  articulation?: string;
@@ -117,7 +116,7 @@ export interface NoteJSON {
117
116
  tuplet?: Tuplet;
118
117
  hairpin?: Hairpin;
119
118
  isGrace?: boolean;
120
- lyric?: string;
119
+ lyric?: Lyric;
121
120
  chord?: string;
122
121
  glissando?: Glissando;
123
122
  arpeggio?: Arpeggio;
@@ -127,7 +126,7 @@ export interface NoteJSON {
127
126
  fret?: number;
128
127
  string?: number;
129
128
  fretboardDiagram?: FretboardDiagram;
130
- lyrics?: (string | Lyric)[];
129
+ lyrics?: Lyric[];
131
130
  staffText?: string;
132
131
  color?: string;
133
132
  notehead?: string;