@scorelabs/core 1.0.7 → 1.0.10

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 (78) hide show
  1. package/dist/importers/MusicXMLParser.d.ts +7 -2
  2. package/dist/importers/MusicXMLParser.js +261 -103
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +2 -2
  5. package/dist/models/Instrument.d.ts +8 -19
  6. package/dist/models/Instrument.js +77 -23
  7. package/dist/models/Measure.d.ts +5 -1
  8. package/dist/models/Measure.js +132 -19
  9. package/dist/models/Note.d.ts +7 -7
  10. package/dist/models/Note.js +2 -2
  11. package/dist/models/NoteSet.d.ts +44 -42
  12. package/dist/models/NoteSet.js +5 -2
  13. package/dist/models/Pitch.js +4 -0
  14. package/dist/models/PreMeasure.d.ts +24 -0
  15. package/dist/models/PreMeasure.js +30 -0
  16. package/dist/models/Score.d.ts +20 -5
  17. package/dist/models/Score.js +190 -5
  18. package/dist/models/index.d.ts +1 -0
  19. package/dist/models/index.js +1 -0
  20. package/dist/models/types.d.ts +2 -213
  21. package/dist/models/types.js +2 -213
  22. package/dist/types/Accidental.d.ts +7 -0
  23. package/dist/types/Accidental.js +8 -0
  24. package/dist/types/Arpeggio.d.ts +5 -0
  25. package/dist/types/Arpeggio.js +6 -0
  26. package/dist/types/Articulation.d.ts +10 -0
  27. package/dist/types/Articulation.js +11 -0
  28. package/dist/types/BarlineStyle.d.ts +9 -0
  29. package/dist/types/BarlineStyle.js +10 -0
  30. package/dist/types/Bowing.d.ts +4 -0
  31. package/dist/types/Bowing.js +5 -0
  32. package/dist/types/Clef.d.ts +10 -0
  33. package/dist/types/Clef.js +11 -0
  34. package/dist/types/Duration.d.ts +20 -0
  35. package/dist/types/Duration.js +60 -0
  36. package/dist/types/Dynamic.d.ts +12 -0
  37. package/dist/types/Dynamic.js +13 -0
  38. package/dist/types/Fretboard.d.ts +19 -0
  39. package/dist/types/Fretboard.js +1 -0
  40. package/dist/types/Genre.d.ts +27 -0
  41. package/dist/types/Genre.js +28 -0
  42. package/dist/types/Glissando.d.ts +8 -0
  43. package/dist/types/Glissando.js +5 -0
  44. package/dist/types/Hairpin.d.ts +10 -0
  45. package/dist/types/Hairpin.js +11 -0
  46. package/dist/types/InstrumentPreset.d.ts +11 -0
  47. package/dist/types/InstrumentPreset.js +12 -0
  48. package/dist/types/InstrumentType.d.ts +8 -0
  49. package/dist/types/InstrumentType.js +9 -0
  50. package/dist/types/KeySignature.d.ts +3 -0
  51. package/dist/types/KeySignature.js +1 -0
  52. package/dist/types/Lyric.d.ts +11 -0
  53. package/dist/types/Lyric.js +7 -0
  54. package/dist/types/NoteheadShape.d.ts +8 -0
  55. package/dist/types/NoteheadShape.js +9 -0
  56. package/dist/types/Ornament.d.ts +8 -0
  57. package/dist/types/Ornament.js +9 -0
  58. package/dist/types/Ottava.d.ts +10 -0
  59. package/dist/types/Ottava.js +7 -0
  60. package/dist/types/Pedal.d.ts +4 -0
  61. package/dist/types/Pedal.js +1 -0
  62. package/dist/types/Repeat.d.ts +8 -0
  63. package/dist/types/Repeat.js +1 -0
  64. package/dist/types/Slur.d.ts +4 -0
  65. package/dist/types/Slur.js +1 -0
  66. package/dist/types/StemDirection.d.ts +4 -0
  67. package/dist/types/StemDirection.js +5 -0
  68. package/dist/types/Tempo.d.ts +8 -0
  69. package/dist/types/Tempo.js +1 -0
  70. package/dist/types/TimeSignature.d.ts +6 -0
  71. package/dist/types/TimeSignature.js +3 -0
  72. package/dist/types/Tuplet.d.ts +5 -0
  73. package/dist/types/Tuplet.js +1 -0
  74. package/dist/types/User.d.ts +21 -0
  75. package/dist/types/User.js +7 -0
  76. package/dist/types/index.d.ts +27 -0
  77. package/dist/types/index.js +27 -0
  78. package/package.json +3 -1
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export * from './models';
2
- export * from './importers';
1
+ export * from './models/index.js';
2
+ export * from './importers/index.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export * from './models';
2
- export * from './importers';
1
+ export * from './models/index.js';
2
+ export * from './importers/index.js';
@@ -1,27 +1,16 @@
1
- export declare enum InstrumentType {
2
- String = "string",
3
- Brass = "brass",
4
- Woodwind = "woodwind",
5
- Percussion = "percussion",
6
- Keyboard = "keyboard",
7
- Synth = "synth"
8
- }
9
- export declare enum InstrumentPreset {
10
- Piano = "piano",
11
- Violin = "violin",
12
- Cello = "cello",
13
- Guitar = "guitar",
14
- ElectricGuitar = "electric-guitar",
15
- Bass = "bass",
16
- Flute = "flute",
17
- Trumpet = "trumpet",
18
- Drums = "drums"
19
- }
1
+ import { InstrumentPreset } from '../types/InstrumentPreset.js';
2
+ import { InstrumentType } from '../types/InstrumentType.js';
20
3
  export interface Instrument {
21
4
  name: string;
22
5
  midiProgram: number;
6
+ sound: InstrumentPreset;
23
7
  type?: InstrumentType;
24
8
  transposition?: number;
25
9
  }
26
10
  export declare const PRESET_INSTRUMENTS: Record<string, Instrument>;
11
+ export declare const TRANSLATED_INSTRUMENT_NAMES: Record<string, Record<InstrumentPreset, string>>;
27
12
  export declare function getInstrumentByProgram(program: number): Instrument;
13
+ /**
14
+ * Guesses the instrument based on a string (like part name).
15
+ */
16
+ export declare function guessInstrumentByName(name: string): Instrument;
@@ -1,67 +1,121 @@
1
- export var InstrumentType;
2
- (function (InstrumentType) {
3
- InstrumentType["String"] = "string";
4
- InstrumentType["Brass"] = "brass";
5
- InstrumentType["Woodwind"] = "woodwind";
6
- InstrumentType["Percussion"] = "percussion";
7
- InstrumentType["Keyboard"] = "keyboard";
8
- InstrumentType["Synth"] = "synth";
9
- })(InstrumentType || (InstrumentType = {}));
10
- export var InstrumentPreset;
11
- (function (InstrumentPreset) {
12
- InstrumentPreset["Piano"] = "piano";
13
- InstrumentPreset["Violin"] = "violin";
14
- InstrumentPreset["Cello"] = "cello";
15
- InstrumentPreset["Guitar"] = "guitar";
16
- InstrumentPreset["ElectricGuitar"] = "electric-guitar";
17
- InstrumentPreset["Bass"] = "bass";
18
- InstrumentPreset["Flute"] = "flute";
19
- InstrumentPreset["Trumpet"] = "trumpet";
20
- InstrumentPreset["Drums"] = "drums";
21
- })(InstrumentPreset || (InstrumentPreset = {}));
1
+ import { InstrumentPreset } from '../types/InstrumentPreset.js';
2
+ import { InstrumentType } from '../types/InstrumentType.js';
22
3
  export const PRESET_INSTRUMENTS = {
23
4
  [InstrumentPreset.Piano]: {
24
5
  name: 'Acoustic Grand Piano',
25
6
  midiProgram: 0,
7
+ sound: InstrumentPreset.Piano,
26
8
  type: InstrumentType.Keyboard,
27
9
  },
28
- [InstrumentPreset.Violin]: { name: 'Violin', midiProgram: 40, type: InstrumentType.String },
29
- [InstrumentPreset.Cello]: { name: 'Cello', midiProgram: 42, type: InstrumentType.String },
10
+ [InstrumentPreset.Violin]: {
11
+ name: 'Violin',
12
+ midiProgram: 40,
13
+ sound: InstrumentPreset.Violin,
14
+ type: InstrumentType.String,
15
+ },
16
+ [InstrumentPreset.Cello]: {
17
+ name: 'Cello',
18
+ midiProgram: 42,
19
+ sound: InstrumentPreset.Cello,
20
+ type: InstrumentType.String,
21
+ },
30
22
  [InstrumentPreset.Guitar]: {
31
23
  name: 'Acoustic Guitar (nylon)',
32
24
  midiProgram: 24,
25
+ sound: InstrumentPreset.Guitar,
33
26
  type: InstrumentType.String,
34
27
  },
35
28
  [InstrumentPreset.ElectricGuitar]: {
36
29
  name: 'Electric Guitar (clean)',
37
30
  midiProgram: 27,
31
+ sound: InstrumentPreset.ElectricGuitar,
38
32
  type: InstrumentType.String,
39
33
  },
40
34
  [InstrumentPreset.Bass]: {
41
35
  name: 'Acoustic Bass',
42
36
  midiProgram: 32,
37
+ sound: InstrumentPreset.Bass,
43
38
  type: InstrumentType.String,
44
39
  },
45
40
  [InstrumentPreset.Flute]: {
46
41
  name: 'Flute',
47
42
  midiProgram: 73,
43
+ sound: InstrumentPreset.Flute,
48
44
  type: InstrumentType.Woodwind,
49
45
  transposition: 0,
50
46
  },
51
47
  [InstrumentPreset.Trumpet]: {
52
48
  name: 'Trumpet',
53
49
  midiProgram: 56,
50
+ sound: InstrumentPreset.Trumpet,
54
51
  type: InstrumentType.Brass,
55
52
  transposition: -2,
56
53
  },
57
54
  [InstrumentPreset.Drums]: {
58
55
  name: 'Drum Kit',
59
56
  midiProgram: 118,
57
+ sound: InstrumentPreset.Drums,
60
58
  type: InstrumentType.Percussion,
61
59
  transposition: 0,
62
60
  },
63
61
  };
62
+ export const TRANSLATED_INSTRUMENT_NAMES = {
63
+ en: {
64
+ [InstrumentPreset.Piano]: 'Acoustic Grand Piano',
65
+ [InstrumentPreset.Violin]: 'Violin',
66
+ [InstrumentPreset.Cello]: 'Cello',
67
+ [InstrumentPreset.Guitar]: 'Acoustic Guitar',
68
+ [InstrumentPreset.ElectricGuitar]: 'Electric Guitar',
69
+ [InstrumentPreset.Bass]: 'Acoustic Bass',
70
+ [InstrumentPreset.Flute]: 'Flute',
71
+ [InstrumentPreset.Trumpet]: 'Trumpet',
72
+ [InstrumentPreset.Drums]: 'Drum Kit',
73
+ },
74
+ ca: {
75
+ [InstrumentPreset.Piano]: 'Piano',
76
+ [InstrumentPreset.Violin]: 'Violí',
77
+ [InstrumentPreset.Cello]: 'Violoncel',
78
+ [InstrumentPreset.Guitar]: 'Guitarra Acústica',
79
+ [InstrumentPreset.ElectricGuitar]: 'Guitarra Elèctrica',
80
+ [InstrumentPreset.Bass]: 'Baix',
81
+ [InstrumentPreset.Flute]: 'Flauta',
82
+ [InstrumentPreset.Trumpet]: 'Trompeta',
83
+ [InstrumentPreset.Drums]: 'Bateria',
84
+ },
85
+ };
64
86
  export function getInstrumentByProgram(program) {
65
87
  const match = Object.values(PRESET_INSTRUMENTS).find((i) => i.midiProgram === program);
66
88
  return match || PRESET_INSTRUMENTS[InstrumentPreset.Piano];
67
89
  }
90
+ /**
91
+ * Guesses the instrument based on a string (like part name).
92
+ */
93
+ export function guessInstrumentByName(name) {
94
+ const n = name.toLowerCase();
95
+ if (n.includes('piano'))
96
+ return PRESET_INSTRUMENTS[InstrumentPreset.Piano];
97
+ if (n.includes('violin'))
98
+ return PRESET_INSTRUMENTS[InstrumentPreset.Violin];
99
+ if (n.includes('cello') || n.includes('violoncel'))
100
+ return PRESET_INSTRUMENTS[InstrumentPreset.Cello];
101
+ if (n.includes('electric guitar'))
102
+ return PRESET_INSTRUMENTS[InstrumentPreset.ElectricGuitar];
103
+ if (n.includes('guitar'))
104
+ return PRESET_INSTRUMENTS[InstrumentPreset.Guitar];
105
+ if (n.includes('bass') || n.includes('baixo') || n.includes('baix'))
106
+ return PRESET_INSTRUMENTS[InstrumentPreset.Bass];
107
+ if (n.includes('flute') || n.includes('flauta'))
108
+ return PRESET_INSTRUMENTS[InstrumentPreset.Flute];
109
+ if (n.includes('trumpet') || n.includes('trompeta'))
110
+ return PRESET_INSTRUMENTS[InstrumentPreset.Trumpet];
111
+ if (n.includes('drum') || n.includes('perc') || n.includes('bateria'))
112
+ return PRESET_INSTRUMENTS[InstrumentPreset.Drums];
113
+ // Try partial matches for strings, brass, etc.
114
+ if (n.includes('string'))
115
+ return PRESET_INSTRUMENTS[InstrumentPreset.Violin];
116
+ if (n.includes('brass'))
117
+ return PRESET_INSTRUMENTS[InstrumentPreset.Trumpet];
118
+ if (n.includes('wind'))
119
+ return PRESET_INSTRUMENTS[InstrumentPreset.Flute];
120
+ return PRESET_INSTRUMENTS[InstrumentPreset.Piano];
121
+ }
@@ -20,6 +20,8 @@ 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
+ get hasStartRepeat(): boolean;
24
+ get hasEndRepeat(): boolean;
23
25
  changeNoteDuration(noteIndex: number, newDuration: Duration, isDotted?: boolean, voiceIndex?: number): Measure;
24
26
  getTotalDuration(voiceIndex?: number): number;
25
27
  static fromJSON(data: MeasureJSON): Measure;
@@ -27,7 +29,7 @@ export declare class Measure {
27
29
  replaceNoteSet(index: number, newNoteSet: NoteSet, voiceIndex?: number): Measure;
28
30
  replaceNote(noteIndex: number, newNote: Note, voiceIndex?: number): Measure;
29
31
  deleteNote(noteIndex: number, voiceIndex?: number): Measure;
30
- autoBeam(timeSign: TimeSignature): Measure;
32
+ autoBeam(timeSign?: TimeSignature): Measure;
31
33
  withTimeSignature(timeSignature?: TimeSignature): Measure;
32
34
  withKeySignature(keySignature?: KeySignature): Measure;
33
35
  withClef(clef?: Clef): Measure;
@@ -44,6 +46,8 @@ export declare class Measure {
44
46
  withSystemText(text?: string): Measure;
45
47
  withBarlineStyle(style: BarlineStyle): Measure;
46
48
  fillVoiceWithRests(voiceIndex: number, targetDuration: number): Measure;
49
+ simplifyRests(voiceIndex?: number): Measure;
50
+ private decomposeToRests;
47
51
  toJSON(): MeasureJSON;
48
52
  }
49
53
  export interface MeasureJSON {
@@ -1,6 +1,6 @@
1
1
  import { Note } from './Note';
2
2
  import { NoteSet } from './NoteSet';
3
- import { BarlineStyle, DURATION_VALUES, decomposeDuration, } from './types';
3
+ import { BarlineStyle, DURATION_VALUES, Duration, decomposeDuration, } from './types';
4
4
  /**
5
5
  * Represents a single measure containing note sets, potentially in multiple voices.
6
6
  */
@@ -39,6 +39,12 @@ export class Measure {
39
39
  get notes() {
40
40
  return this.voices[0] || [];
41
41
  }
42
+ get hasStartRepeat() {
43
+ return this.repeats.some((r) => r.type === "start");
44
+ }
45
+ get hasEndRepeat() {
46
+ return this.repeats.some((r) => r.type === "end");
47
+ }
42
48
  changeNoteDuration(noteIndex, newDuration, isDotted = false, voiceIndex = 0) {
43
49
  const voice = this.voices[voiceIndex];
44
50
  if (!voice || noteIndex < 0 || noteIndex >= voice.length)
@@ -88,7 +94,11 @@ export class Measure {
88
94
  }
89
95
  const newVoices = [...this.voices];
90
96
  newVoices[voiceIndex] = updatedVoice;
91
- return this.withVoices(newVoices);
97
+ let result = this.withVoices(newVoices).simplifyRests(voiceIndex);
98
+ if (this.timeSignature) {
99
+ result = result.autoBeam(this.timeSignature);
100
+ }
101
+ return result;
92
102
  }
93
103
  getTotalDuration(voiceIndex = 0) {
94
104
  const voice = this.voices[voiceIndex];
@@ -135,10 +145,56 @@ export class Measure {
135
145
  const voice = [...this.voices[voiceIndex]];
136
146
  if (index >= voice.length)
137
147
  return this;
138
- voice[index] = newNoteSet;
148
+ const oldVal = voice[index].getDurationValue();
149
+ const newVal = newNoteSet.getDurationValue();
150
+ // Use a copy for modifications
151
+ const updatedVoice = [...voice];
152
+ if (Math.abs(oldVal - newVal) < 0.001) {
153
+ updatedVoice[index] = newNoteSet;
154
+ }
155
+ else if (newVal < oldVal) {
156
+ const gap = oldVal - newVal;
157
+ updatedVoice[index] = newNoteSet;
158
+ const rests = this.decomposeToRests(gap);
159
+ updatedVoice.splice(index + 1, 0, ...rests);
160
+ }
161
+ else {
162
+ const delta = newVal - oldVal;
163
+ let available = 0;
164
+ for (let i = index + 1; i < updatedVoice.length; i++) {
165
+ available += updatedVoice[i].getDurationValue();
166
+ }
167
+ // If there's enough space to consume, do it.
168
+ // If not, we still replace it but it will overflow (which simplifyRests might fix or layout will handle)
169
+ updatedVoice[index] = newNoteSet;
170
+ let consumed = 0;
171
+ let removeCount = 0;
172
+ const replacements = [];
173
+ for (let i = index + 1; i < voice.length; i++) {
174
+ const ns = voice[i];
175
+ const val = ns.getDurationValue();
176
+ if (consumed + val <= delta + 0.001) {
177
+ consumed += val;
178
+ removeCount++;
179
+ if (consumed >= delta - 0.001)
180
+ break;
181
+ }
182
+ else {
183
+ const remaining = consumed + val - delta;
184
+ replacements.push(...this.decomposeToRests(remaining));
185
+ removeCount++;
186
+ break;
187
+ }
188
+ }
189
+ updatedVoice.splice(index + 1, removeCount, ...replacements);
190
+ }
139
191
  const newVoices = [...this.voices];
140
- newVoices[voiceIndex] = voice;
141
- return this.withVoices(newVoices);
192
+ newVoices[voiceIndex] = updatedVoice;
193
+ let result = this.withVoices(newVoices).simplifyRests(voiceIndex);
194
+ if (this.timeSignature) {
195
+ result = result.autoBeam(this.timeSignature);
196
+ }
197
+ return result;
142
198
  }
143
199
  replaceNote(noteIndex, newNote, voiceIndex = 0) {
144
200
  return this.replaceNoteSet(noteIndex, new NoteSet([newNote]), voiceIndex);
@@ -149,13 +205,20 @@ export class Measure {
149
205
  const voice = [...this.voices[voiceIndex]];
150
206
  if (noteIndex < 0 || noteIndex >= voice.length)
151
207
  return this;
152
- voice.splice(noteIndex, 1);
208
+ // Instead of splicing, replace with a rest of the same duration
209
+ const target = voice[noteIndex];
210
+ voice[noteIndex] = target.withRest(true);
153
211
  const newVoices = [...this.voices];
154
212
  newVoices[voiceIndex] = voice;
155
- return this.withVoices(newVoices);
213
+ return this.withVoices(newVoices).simplifyRests(voiceIndex);
156
214
  }
157
215
  autoBeam(timeSign) {
216
+ const ts = timeSign || this.timeSignature;
217
+ if (!ts)
218
+ return this;
158
219
  const newVoices = this.voices.map((voice) => {
220
+ if (voice.length === 0)
221
+ return [];
159
222
  let currentOffset = 0;
160
223
  let lastGroupId = undefined;
161
224
  let lastGroupStartTime = 0;
@@ -165,28 +228,38 @@ export class Measure {
165
228
  const noteDuration = ns.getDurationValue();
166
229
  let groupToAssign = undefined;
167
230
  if (ns.isBeamable()) {
168
- const isSixteenth = noteDuration < 0.5;
169
- let currentBeatLength = 4 / timeSign.beatType;
170
- if (timeSign.beatType === 8 && timeSign.beats % 3 === 0) {
171
- currentBeatLength = 1.5;
172
- }
173
- else if (timeSign.beatType === 4 && (timeSign.beats === 4 || timeSign.beats === 2)) {
174
- currentBeatLength = lastGroupHasSixteenths || isSixteenth ? 1.0 : 2.0;
231
+ const isSixteenth = noteDuration < 0.5 - 0.001;
232
+ // Determine the logical "beat length" for beaming purposes
233
+ let beamBeatLength = 4 / ts.beatType; // Default is one beat
234
+ if (ts.beatType === 4) {
235
+ if (ts.beats === 4 || ts.beats === 2) {
236
+ // In 4/4 or 2/4, eighth notes are often beamed in groups of 2 beats (length 2.0)
237
+ // unless there are sixteenth notes which force 1-beat groupings.
238
+ beamBeatLength = (lastGroupHasSixteenths || isSixteenth) ? 1.0 : 2.0;
239
+ }
175
240
  }
176
- else if (timeSign.beatType === 2 && timeSign.beats === 2) {
177
- currentBeatLength = lastGroupHasSixteenths || isSixteenth ? 1.0 : 2.0;
241
+ else if (ts.beatType === 8) {
242
+ if (ts.beats % 3 === 0) {
243
+ // Compound meter (6/8, 9/8, 12/8): Beam by 3 eighth notes (1.5 length)
244
+ beamBeatLength = 1.5;
245
+ }
246
+ else {
247
+ // Simple meter in 8 (e.g. 2/8, 4/8): Beam by quarter note (1.0 length)
248
+ beamBeatLength = 1.0;
249
+ }
178
250
  }
179
- const currentBeatId = Math.floor(currentOffset / currentBeatLength + 0.001);
251
+ const currentBeatId = Math.floor(currentOffset / beamBeatLength + 0.001);
180
252
  const endOffset = currentOffset + noteDuration;
181
- const crossesBeat = Math.floor(endOffset / currentBeatLength - 0.001) !== currentBeatId;
253
+ const crossesBeat = Math.floor(endOffset / beamBeatLength - 0.001) !== currentBeatId;
182
254
  const sameBeatAsLast = lastGroupId !== undefined &&
183
- Math.floor(lastGroupStartTime / currentBeatLength + 0.001) === currentBeatId;
255
+ Math.floor(lastGroupStartTime / beamBeatLength + 0.001) === currentBeatId;
184
256
  if (sameBeatAsLast && !crossesBeat) {
185
257
  groupToAssign = lastGroupId;
186
258
  if (isSixteenth)
187
259
  lastGroupHasSixteenths = true;
188
260
  }
189
261
  else {
262
+ // Start a new group
190
263
  lastGroupId = Math.floor(Math.random() * 1000000) + 1;
191
264
  lastGroupStartTime = currentOffset;
192
265
  lastGroupHasSixteenths = isSixteenth;
@@ -194,12 +267,14 @@ export class Measure {
194
267
  }
195
268
  }
196
269
  else {
270
+ // Non-beamable note (Quarter, Half, etc.) or Rest
197
271
  lastGroupId = undefined;
198
272
  lastGroupHasSixteenths = false;
199
273
  }
200
274
  tempNoteSets.push(ns.withBeamGroup(groupToAssign));
201
275
  currentOffset += noteDuration;
202
276
  }
277
+ // Cleanup: groups of 1 note are not beamed
203
278
  const groupCounts = new Map();
204
279
  for (const ns of tempNoteSets) {
205
280
  if (ns.beamGroup !== undefined) {
@@ -288,6 +363,44 @@ export class Measure {
288
363
  }
289
364
  return this.withVoices(newVoices);
290
365
  }
366
+ simplifyRests(voiceIndex = 0) {
367
+ const voice = this.voices[voiceIndex];
368
+ if (!voice || voice.length === 0)
369
+ return this;
370
+ const totalDur = this.getTotalDuration(voiceIndex);
371
+ const expectedDur = this.timeSignature ? (this.timeSignature.beats * 4) / this.timeSignature.beatType : 4.0;
372
+ // 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);
374
+ if (allRests && Math.abs(totalDur - expectedDur) < 0.001) {
375
+ const newVoice = [new NoteSet([new Note(Duration.Whole, undefined, true)])];
376
+ const newVoices = [...this.voices];
377
+ newVoices[voiceIndex] = newVoice;
378
+ return this.withVoices(newVoices);
379
+ }
380
+ const newVoice = [];
381
+ let currentRestSum = 0;
382
+ for (const ns of voice) {
383
+ if (ns.isRest) {
384
+ currentRestSum += ns.getDurationValue();
385
+ }
386
+ else {
387
+ if (currentRestSum > 0.001) {
388
+ newVoice.push(...this.decomposeToRests(currentRestSum));
389
+ currentRestSum = 0;
390
+ }
391
+ newVoice.push(ns);
392
+ }
393
+ }
394
+ if (currentRestSum > 0.001) {
395
+ newVoice.push(...this.decomposeToRests(currentRestSum));
396
+ }
397
+ const newVoices = [...this.voices];
398
+ newVoices[voiceIndex] = newVoice;
399
+ return this.withVoices(newVoices);
400
+ }
401
+ decomposeToRests(value) {
402
+ return decomposeDuration(value).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
403
+ }
291
404
  toJSON() {
292
405
  return {
293
406
  voices: this.voices.map((v) => v.map((ns) => ns.toJSON())),
@@ -17,7 +17,7 @@ export declare class Note {
17
17
  readonly tuplet?: Tuplet | undefined;
18
18
  readonly hairpin?: Hairpin | undefined;
19
19
  readonly isGrace: boolean;
20
- readonly lyric?: string | undefined;
20
+ readonly lyric?: Lyric | undefined;
21
21
  readonly chord?: string | undefined;
22
22
  readonly glissando?: Glissando | undefined;
23
23
  readonly arpeggio?: Arpeggio | undefined;
@@ -27,7 +27,7 @@ export declare class Note {
27
27
  readonly fret?: number | undefined;
28
28
  readonly string?: number | undefined;
29
29
  readonly fretboardDiagram?: FretboardDiagram | undefined;
30
- readonly lyrics?: (string | Lyric)[] | undefined;
30
+ readonly lyrics?: Lyric[] | undefined;
31
31
  readonly staffText?: string | undefined;
32
32
  readonly color?: string | undefined;
33
33
  readonly notehead?: NoteheadShape | undefined;
@@ -40,7 +40,7 @@ export declare class Note {
40
40
  readonly pullOff?: "start" | "stop" | undefined;
41
41
  constructor(duration: Duration, pitch?: Pitch | undefined, // undefined for rests
42
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);
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);
44
44
  /**
45
45
  * Get all lyrics as standardized objects
46
46
  */
@@ -70,8 +70,8 @@ export declare class Note {
70
70
  withTie(tie?: boolean): Note;
71
71
  withSlur(slur?: Slur): Note;
72
72
  withTuplet(tuplet?: Tuplet): Note;
73
- withLyric(lyric?: string): Note;
74
- withLyrics(lyrics: (string | Lyric)[]): Note;
73
+ withLyric(lyric?: Lyric): Note;
74
+ withLyrics(lyrics: Lyric[]): Note;
75
75
  withHairpin(hairpin?: Hairpin): Note;
76
76
  withAccidental(accidental?: Accidental): Note;
77
77
  withDuration(duration: Duration, isDotted?: boolean): Note;
@@ -117,7 +117,7 @@ export interface NoteJSON {
117
117
  tuplet?: Tuplet;
118
118
  hairpin?: Hairpin;
119
119
  isGrace?: boolean;
120
- lyric?: string;
120
+ lyric?: Lyric;
121
121
  chord?: string;
122
122
  glissando?: Glissando;
123
123
  arpeggio?: Arpeggio;
@@ -127,7 +127,7 @@ export interface NoteJSON {
127
127
  fret?: number;
128
128
  string?: number;
129
129
  fretboardDiagram?: FretboardDiagram;
130
- lyrics?: (string | Lyric)[];
130
+ lyrics?: Lyric[];
131
131
  staffText?: string;
132
132
  color?: string;
133
133
  notehead?: string;
@@ -81,10 +81,10 @@ export class Note {
81
81
  */
82
82
  get allLyrics() {
83
83
  if (this.lyrics) {
84
- return this.lyrics.map((l) => (typeof l === 'string' ? { text: l } : l));
84
+ return this.lyrics;
85
85
  }
86
86
  if (this.lyric) {
87
- return [{ text: this.lyric }];
87
+ return [this.lyric];
88
88
  }
89
89
  return [];
90
90
  }
@@ -1,5 +1,6 @@
1
1
  import { Note, NoteJSON } from './Note';
2
- import { Duration } from './types';
2
+ import { Pitch } from './Pitch';
3
+ import { Accidental, Arpeggio, Articulation, Bowing, Duration, Dynamic, FretboardDiagram, Glissando, Hairpin, Lyric, NoteheadShape, Ornament, Ottava, Pedal, Slur, StemDirection, Tuplet } from './types';
3
4
  export declare class NoteSet {
4
5
  readonly notes: Note[];
5
6
  constructor(notes: Note[]);
@@ -7,16 +8,16 @@ export declare class NoteSet {
7
8
  get isDotted(): boolean;
8
9
  get isRest(): boolean;
9
10
  get beamGroup(): number | undefined;
10
- get tuplet(): import("./types").Tuplet | undefined;
11
+ get tuplet(): Tuplet | undefined;
11
12
  getDurationValue(): number;
12
13
  isBeamable(): boolean;
13
14
  withDuration(duration: Duration, isDotted?: boolean): NoteSet;
14
15
  withBeamGroup(group?: number): NoteSet;
15
16
  withNotes(notes: Note[]): NoteSet;
16
- withLyrics(lyrics: (string | any)[]): NoteSet;
17
- withTuplet(tuplet?: any): NoteSet;
18
- withPitch(pitch: any): NoteSet;
19
- withAccidental(accidental: any): NoteSet;
17
+ withLyrics(lyrics: Lyric[]): NoteSet;
18
+ withTuplet(tuplet?: Tuplet): NoteSet;
19
+ withPitch(pitch: Pitch): NoteSet;
20
+ withAccidental(accidental?: Accidental): NoteSet;
20
21
  withRest(isRest: boolean): NoteSet;
21
22
  withGrace(isGrace: boolean): NoteSet;
22
23
  toggleEnharmonic(): NoteSet;
@@ -26,58 +27,59 @@ export declare class NoteSet {
26
27
  withChord(chord?: string): NoteSet;
27
28
  withTab(fret: number, string: number): NoteSet;
28
29
  withTie(tie: boolean): NoteSet;
29
- withSlur(slur?: any): NoteSet;
30
- withArticulation(articulation?: any): NoteSet;
31
- withOrnament(ornament?: any): NoteSet;
32
- withDynamic(dynamic?: any): NoteSet;
33
- withHairpin(hairpin?: any): NoteSet;
34
- withGlissando(glissando?: any): NoteSet;
35
- withArpeggio(arpeggio?: any): NoteSet;
36
- withOttava(ottava?: any): NoteSet;
37
- withPedal(pedal?: any): NoteSet;
30
+ withSlur(slur?: Slur): NoteSet;
31
+ withArticulation(articulation?: Articulation): NoteSet;
32
+ withOrnament(ornament?: Ornament): NoteSet;
33
+ withDynamic(dynamic?: Dynamic): NoteSet;
34
+ withHairpin(hairpin?: Hairpin): NoteSet;
35
+ withGlissando(glissando?: Glissando): NoteSet;
36
+ withArpeggio(arpeggio?: Arpeggio): NoteSet;
37
+ withOttava(ottava?: Ottava): NoteSet;
38
+ withPedal(pedal?: Pedal): NoteSet;
38
39
  withColor(color?: string): NoteSet;
39
- withNotehead(notehead: any): NoteSet;
40
- withBowing(bowing?: any): NoteSet;
40
+ withNotehead(notehead?: NoteheadShape): NoteSet;
41
+ withBowing(bowing?: Bowing): NoteSet;
41
42
  withFingering(fingering?: number): NoteSet;
42
43
  withHammerOn(type?: 'start' | 'stop'): NoteSet;
43
44
  withPullOff(type?: 'start' | 'stop'): NoteSet;
44
45
  withPalmMute(type?: 'start' | 'stop'): NoteSet;
45
46
  withStringCircled(isStringCircled?: boolean): NoteSet;
46
- withStemDirection(dir?: any): NoteSet;
47
- withLyric(lyric?: any): NoteSet;
47
+ withStemDirection(dir?: StemDirection): NoteSet;
48
+ withLyric(lyric?: Lyric): NoteSet;
48
49
  withStaffText(text?: string): NoteSet;
49
- withFretboardDiagram(diagram?: any): NoteSet;
50
- get pitch(): import("./Pitch").Pitch | undefined;
51
- get accidental(): import("./types").Accidental | undefined;
50
+ withFretboardDiagram(diagram?: FretboardDiagram): NoteSet;
51
+ get pitch(): Pitch | undefined;
52
+ get accidental(): Accidental | undefined;
52
53
  get tie(): boolean | undefined;
53
- get slur(): import("./types").Slur | undefined;
54
- get articulation(): import("./types").Articulation | undefined;
55
- get ornament(): import("./types").Ornament | undefined;
56
- get dynamic(): import("./types").Dynamic | undefined;
57
- get hairpin(): import("./types").Hairpin | undefined;
58
- get glissando(): import("./types").Glissando | undefined;
59
- get arpeggio(): import("./types").Arpeggio | undefined;
60
- get ottava(): import("./types").Ottava | undefined;
61
- get pedal(): import("./types").Pedal | undefined;
62
- get notehead(): import("./types").NoteheadShape | undefined;
54
+ get slur(): Slur | undefined;
55
+ get articulations(): Articulation[] | undefined;
56
+ get articulation(): Articulation | undefined;
57
+ get ornament(): Ornament | undefined;
58
+ get dynamic(): Dynamic | undefined;
59
+ get hairpin(): Hairpin | undefined;
60
+ get glissando(): Glissando | undefined;
61
+ get arpeggio(): Arpeggio | undefined;
62
+ get ottava(): Ottava | undefined;
63
+ get pedal(): Pedal | undefined;
64
+ get notehead(): NoteheadShape | undefined;
63
65
  get color(): string | undefined;
64
66
  get fret(): number | undefined;
65
67
  get string(): number | undefined;
66
- get bowing(): import("./types").Bowing | undefined;
68
+ get bowing(): Bowing | undefined;
67
69
  get fingering(): number | undefined;
68
- get allLyrics(): import("./types").Lyric[];
69
- get lyrics(): (string | import("./types").Lyric)[] | undefined;
70
- get lyric(): string | undefined;
70
+ get allLyrics(): Lyric[];
71
+ get lyrics(): Lyric[] | undefined;
72
+ get lyric(): Lyric | undefined;
71
73
  get chord(): string | undefined;
72
- get fretboardDiagram(): import("./types").FretboardDiagram | undefined;
74
+ get fretboardDiagram(): FretboardDiagram | undefined;
73
75
  get staffText(): string | undefined;
74
- get hammerOn(): "start" | "stop" | undefined;
75
- get pullOff(): "start" | "stop" | undefined;
76
- get palmMute(): "start" | "stop" | undefined;
76
+ get hammerOn(): 'start' | 'stop' | undefined;
77
+ get pullOff(): 'start' | 'stop' | undefined;
78
+ get palmMute(): 'start' | 'stop' | undefined;
77
79
  get isStringCircled(): boolean | undefined;
78
- get stemDirection(): import("./types").StemDirection | undefined;
80
+ get stemDirection(): StemDirection | undefined;
79
81
  toJSON(): NoteSetJSON;
80
- static fromJSON(data: any): NoteSet;
82
+ static fromJSON(data: Partial<NoteSetJSON> | NoteJSON | NoteJSON[]): NoteSet;
81
83
  }
82
84
  export interface NoteSetJSON {
83
85
  notes: NoteJSON[];