@scorelabs/core 1.0.5 → 1.0.9

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 (82) hide show
  1. package/README.md +1 -1
  2. package/dist/importers/MusicXMLParser.d.ts +2 -1
  3. package/dist/importers/MusicXMLParser.js +102 -29
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +2 -2
  6. package/dist/models/Instrument.d.ts +8 -19
  7. package/dist/models/Instrument.js +77 -23
  8. package/dist/models/Measure.d.ts +3 -1
  9. package/dist/models/Measure.js +7 -1
  10. package/dist/models/Note.d.ts +1 -1
  11. package/dist/models/Note.js +1 -1
  12. package/dist/models/NoteSet.d.ts +40 -38
  13. package/dist/models/NoteSet.js +5 -2
  14. package/dist/models/Part.d.ts +2 -2
  15. package/dist/models/Part.js +1 -1
  16. package/dist/models/Pitch.js +4 -0
  17. package/dist/models/PreMeasure.d.ts +24 -0
  18. package/dist/models/PreMeasure.js +30 -0
  19. package/dist/models/Score.d.ts +18 -3
  20. package/dist/models/Score.js +196 -8
  21. package/dist/models/Staff.d.ts +2 -2
  22. package/dist/models/index.d.ts +1 -0
  23. package/dist/models/index.js +1 -0
  24. package/dist/models/types.d.ts +2 -213
  25. package/dist/models/types.js +2 -213
  26. package/dist/types/Accidental.d.ts +7 -0
  27. package/dist/types/Accidental.js +8 -0
  28. package/dist/types/Arpeggio.d.ts +5 -0
  29. package/dist/types/Arpeggio.js +6 -0
  30. package/dist/types/Articulation.d.ts +10 -0
  31. package/dist/types/Articulation.js +11 -0
  32. package/dist/types/BarlineStyle.d.ts +9 -0
  33. package/dist/types/BarlineStyle.js +10 -0
  34. package/dist/types/Bowing.d.ts +4 -0
  35. package/dist/types/Bowing.js +5 -0
  36. package/dist/types/Clef.d.ts +10 -0
  37. package/dist/types/Clef.js +11 -0
  38. package/dist/types/Duration.d.ts +20 -0
  39. package/dist/types/Duration.js +60 -0
  40. package/dist/types/Dynamic.d.ts +12 -0
  41. package/dist/types/Dynamic.js +13 -0
  42. package/dist/types/Fretboard.d.ts +19 -0
  43. package/dist/types/Fretboard.js +1 -0
  44. package/dist/types/Genre.d.ts +27 -0
  45. package/dist/types/Genre.js +28 -0
  46. package/dist/types/Glissando.d.ts +8 -0
  47. package/dist/types/Glissando.js +5 -0
  48. package/dist/types/Hairpin.d.ts +10 -0
  49. package/dist/types/Hairpin.js +11 -0
  50. package/dist/types/InstrumentPreset.d.ts +11 -0
  51. package/dist/types/InstrumentPreset.js +12 -0
  52. package/dist/types/InstrumentType.d.ts +8 -0
  53. package/dist/types/InstrumentType.js +9 -0
  54. package/dist/types/KeySignature.d.ts +3 -0
  55. package/dist/types/KeySignature.js +1 -0
  56. package/dist/types/Lyric.d.ts +11 -0
  57. package/dist/types/Lyric.js +7 -0
  58. package/dist/types/NoteheadShape.d.ts +8 -0
  59. package/dist/types/NoteheadShape.js +9 -0
  60. package/dist/types/Ornament.d.ts +8 -0
  61. package/dist/types/Ornament.js +9 -0
  62. package/dist/types/Ottava.d.ts +10 -0
  63. package/dist/types/Ottava.js +7 -0
  64. package/dist/types/Pedal.d.ts +4 -0
  65. package/dist/types/Pedal.js +1 -0
  66. package/dist/types/Repeat.d.ts +8 -0
  67. package/dist/types/Repeat.js +1 -0
  68. package/dist/types/Slur.d.ts +4 -0
  69. package/dist/types/Slur.js +1 -0
  70. package/dist/types/StemDirection.d.ts +4 -0
  71. package/dist/types/StemDirection.js +5 -0
  72. package/dist/types/Tempo.d.ts +8 -0
  73. package/dist/types/Tempo.js +1 -0
  74. package/dist/types/TimeSignature.d.ts +6 -0
  75. package/dist/types/TimeSignature.js +3 -0
  76. package/dist/types/Tuplet.d.ts +5 -0
  77. package/dist/types/Tuplet.js +1 -0
  78. package/dist/types/index.d.ts +26 -0
  79. package/dist/types/index.js +26 -0
  80. package/package.json +5 -4
  81. package/dist/utils/tier.d.ts +0 -36
  82. package/dist/utils/tier.js +0 -112
package/README.md CHANGED
@@ -36,7 +36,7 @@ console.log(`Number of Parts: ${score.parts.length}`);
36
36
  The core model uses `NoteSet` to represent a collection of notes at a specific point data (e.g., a chord or a single note).
37
37
 
38
38
  ```typescript
39
- import { Note, NoteSet, Pitch, Duration } from '@scorelabs/core';
39
+ import { Duration, Note, NoteSet, Pitch } from '@scorelabs/core';
40
40
 
41
41
  // Create a C4 quarter note
42
42
  const c4 = new Note(new Pitch('C', 4), Duration.Quarter);
@@ -12,8 +12,9 @@ export declare class MusicXMLParser {
12
12
  private currentSymbol;
13
13
  private instrumentPitchMap;
14
14
  private _domParser;
15
- constructor(domParserInstance?: any);
15
+ constructor(domParserInstance?: DOMParser | unknown);
16
16
  private parseFromString;
17
+ static getXMLFromBinary(data: ArrayBuffer, domParser?: DOMParser | unknown): Promise<string>;
17
18
  parseBinary(data: ArrayBuffer): Promise<ScoreJSON>;
18
19
  parse(xmlString: string): ScoreJSON;
19
20
  private parseSubtitle;
@@ -1,6 +1,6 @@
1
1
  import JSZip from 'jszip';
2
- import { getInstrumentByProgram } from '../models/Instrument';
3
- import { Clef, Duration, Accidental, Articulation, BarlineStyle, HairpinType, OttavaType, Ornament, Bowing, StemDirection, GlissandoType, NoteheadShape, Arpeggio, } from '../models/types';
2
+ import { getInstrumentByProgram, guessInstrumentByName } from '../models/Instrument';
3
+ import { Accidental, Arpeggio, Articulation, BarlineStyle, Bowing, Clef, DURATION_VALUES, Duration, GlissandoType, HairpinType, NoteheadShape, Ornament, OttavaType, StemDirection, decomposeDuration, } from '../models/types';
4
4
  /**
5
5
  * MusicXML Parser
6
6
  *
@@ -24,14 +24,17 @@ export class MusicXMLParser {
24
24
  }
25
25
  return this._domParser.parseFromString(xmlString, 'application/xml');
26
26
  }
27
- async parseBinary(data) {
27
+ static async getXMLFromBinary(data, domParser) {
28
28
  const uint8 = new Uint8Array(data);
29
29
  if (uint8[0] === 0x50 && uint8[1] === 0x4b) {
30
30
  const zip = await JSZip.loadAsync(data);
31
31
  const containerXml = await zip.file('META-INF/container.xml')?.async('text');
32
32
  if (!containerXml)
33
33
  throw new Error('Invalid MXL: Missing META-INF/container.xml');
34
- const containerDoc = this.parseFromString(containerXml);
34
+ const _parser = domParser || (typeof DOMParser !== 'undefined' ? new DOMParser() : null);
35
+ if (!_parser)
36
+ throw new Error('No DOMParser available');
37
+ const containerDoc = _parser.parseFromString(containerXml, 'application/xml');
35
38
  const rootfile = containerDoc.querySelector('rootfile');
36
39
  const fullPath = rootfile?.getAttribute('full-path');
37
40
  if (!fullPath)
@@ -39,13 +42,17 @@ export class MusicXMLParser {
39
42
  const scoreXml = await zip.file(fullPath)?.async('text');
40
43
  if (!scoreXml)
41
44
  throw new Error(`Invalid MXL: Could not find file ${fullPath}`);
42
- return this.parse(scoreXml);
45
+ return scoreXml;
43
46
  }
44
47
  else {
45
48
  const decoder = new TextDecoder();
46
- return this.parse(decoder.decode(data));
49
+ return decoder.decode(data);
47
50
  }
48
51
  }
52
+ async parseBinary(data) {
53
+ const scoreXml = await MusicXMLParser.getXMLFromBinary(data, this._domParser);
54
+ return this.parse(scoreXml);
55
+ }
49
56
  parse(xmlString) {
50
57
  this.instrumentPitchMap.clear();
51
58
  // Strip potential BOM and leading garbage
@@ -144,19 +151,28 @@ export class MusicXMLParser {
144
151
  for (const scorePart of Array.from(scoreParts)) {
145
152
  const id = scorePart.getAttribute('id');
146
153
  const name = this.getText(scorePart, 'part-name') || 'Part';
147
- let instrument = getInstrumentByProgram(0); // Default Piano
154
+ let instrument;
148
155
  // Find first midi-instrument to determine main instrument
149
156
  const firstMidiInst = scorePart.querySelector('midi-instrument');
150
157
  if (firstMidiInst) {
151
158
  const programStr = this.getText(firstMidiInst, 'midi-program');
152
159
  if (programStr) {
153
160
  const program = parseInt(programStr);
154
- // MusicXML uses 1-128, MIDI uses 0-127. Adjust if needed.
155
- // Often it's accurate to 1-based, so checking bounds.
156
- // Assuming input is 1-based as per standard
157
161
  instrument = getInstrumentByProgram(Math.max(0, program - 1));
158
162
  }
159
163
  }
164
+ // Fallback: guess by part name
165
+ if (!instrument || instrument.sound === 'piano') {
166
+ const guessed = guessInstrumentByName(name);
167
+ // Only override if the guess is more specific than default piano
168
+ if (guessed.sound !== 'piano') {
169
+ instrument = guessed;
170
+ }
171
+ }
172
+ // Final default
173
+ if (!instrument) {
174
+ instrument = getInstrumentByProgram(0);
175
+ }
160
176
  if (id) {
161
177
  partInfo.set(id, { name: name.trim(), instrument });
162
178
  }
@@ -235,7 +251,8 @@ export class MusicXMLParser {
235
251
  const sign = this.getText(clef, 'sign');
236
252
  if (sign && number <= numStaves) {
237
253
  const line = this.getNumber(clef, 'line') || 0;
238
- staffClefs[number - 1] = this.mapClef(sign, line);
254
+ const octaveChange = this.getNumber(clef, 'clef-octave-change') || 0;
255
+ staffClefs[number - 1] = this.mapClef(sign, line, octaveChange);
239
256
  }
240
257
  });
241
258
  const staffDetails = attributes.querySelectorAll('staff-details');
@@ -251,7 +268,10 @@ export class MusicXMLParser {
251
268
  let systemText = undefined;
252
269
  let contextDynamic = undefined;
253
270
  const { repeats, volta, barlineStyle } = this.parseBarlines(measureElement);
254
- const measureVoices = Array.from({ length: numStaves }, () => []);
271
+ // Map of staffIndex -> Map of voiceId -> NoteSetJSON[]
272
+ const staffVoices = new Map();
273
+ for (let i = 0; i < numStaves; i++)
274
+ staffVoices.set(i, new Map());
255
275
  const children = Array.from(measureElement.children);
256
276
  let pendingChord = undefined;
257
277
  let currentNoteSetMap = new Map();
@@ -368,6 +388,11 @@ export class MusicXMLParser {
368
388
  note.pedal = currentPedal;
369
389
  currentPedal = undefined;
370
390
  }
391
+ const voiceId = this.getText(child, 'voice') || '1';
392
+ const voiceMap = staffVoices.get(staffIdx);
393
+ if (!voiceMap.has(voiceId))
394
+ voiceMap.set(voiceId, []);
395
+ const voiceNoteSets = voiceMap.get(voiceId);
371
396
  if (isChord) {
372
397
  const existing = currentNoteSetMap.get(staffIdx);
373
398
  if (existing)
@@ -378,26 +403,42 @@ export class MusicXMLParser {
378
403
  else {
379
404
  const existing = currentNoteSetMap.get(staffIdx);
380
405
  if (existing)
381
- measureVoices[staffIdx].push({ notes: existing });
406
+ voiceNoteSets.push({ notes: existing });
382
407
  currentNoteSetMap.set(staffIdx, [note]);
383
408
  }
384
409
  }
385
410
  }
386
411
  else if (child.nodeName === 'backup' || child.nodeName === 'forward') {
387
412
  for (const [sIdx, notes] of currentNoteSetMap.entries()) {
388
- if (notes.length > 0)
389
- measureVoices[sIdx].push({ notes });
413
+ if (notes.length > 0) {
414
+ const voiceId = this.getText(child.previousElementSibling, 'voice') || '1';
415
+ const voiceMap = staffVoices.get(sIdx);
416
+ if (!voiceMap.has(voiceId))
417
+ voiceMap.set(voiceId, []);
418
+ voiceMap.get(voiceId).push({ notes });
419
+ }
390
420
  }
391
421
  currentNoteSetMap.clear();
392
422
  }
393
423
  });
394
424
  for (const [sIdx, notes] of currentNoteSetMap.entries()) {
395
- if (notes.length > 0)
396
- measureVoices[sIdx].push({ notes });
425
+ if (notes.length > 0) {
426
+ // Last note in measure
427
+ const voiceId = '1'; // Defaulting to 1 for final push if not tracked
428
+ const voiceMap = staffVoices.get(sIdx);
429
+ if (!voiceMap.has(voiceId))
430
+ voiceMap.set(voiceId, []);
431
+ voiceMap.get(voiceId).push({ notes });
432
+ }
397
433
  }
398
434
  for (let i = 0; i < numStaves; i++) {
435
+ const vMap = staffVoices.get(i);
436
+ let voicesIndices = Array.from(vMap.keys()).sort();
437
+ if (voicesIndices.length === 0)
438
+ voicesIndices = ['1'];
439
+ const voices = voicesIndices.map(id => vMap.get(id) || []);
399
440
  const measure = {
400
- voices: [measureVoices[i]],
441
+ voices: voices,
401
442
  timeSignature: measureTimeSignature,
402
443
  keySignature: measureKeySignature,
403
444
  isPickup: isPickup || undefined,
@@ -408,6 +449,30 @@ export class MusicXMLParser {
408
449
  systemText: i === 0 ? systemText : undefined,
409
450
  barlineStyle,
410
451
  };
452
+ // Padding with rests if not a pickup
453
+ if (!isPickup) {
454
+ const targetDur = (this.currentBeats * 4) / this.currentBeatType;
455
+ measure.voices = measure.voices.map((v) => {
456
+ const currentDur = v.reduce((sum, ns) => {
457
+ const base = DURATION_VALUES[ns.notes[0].duration];
458
+ return sum + (ns.notes[0].isDotted ? base * 1.5 : base);
459
+ }, 0);
460
+ if (currentDur < targetDur - 0.001) {
461
+ const gap = targetDur - currentDur;
462
+ const rests = decomposeDuration(gap).map((d) => ({
463
+ notes: [
464
+ {
465
+ duration: d.duration,
466
+ isRest: true,
467
+ isDotted: d.isDotted,
468
+ },
469
+ ],
470
+ }));
471
+ return [...v, ...rests];
472
+ }
473
+ return v;
474
+ });
475
+ }
411
476
  staves[i].measures.push(measure);
412
477
  staves[i].clef = staffClefs[i];
413
478
  }
@@ -788,22 +853,30 @@ export class MusicXMLParser {
788
853
  else if (barStyle === 'dotted')
789
854
  barlineStyle = BarlineStyle.Dotted;
790
855
  }
791
- if (barline.querySelector('repeat'))
856
+ const repeatEl = barline.querySelector('repeat');
857
+ if (repeatEl) {
858
+ const direction = repeatEl.getAttribute('direction');
859
+ const times = repeatEl.getAttribute('times');
792
860
  repeats.push({
793
- type: barline.querySelector('repeat')?.getAttribute('direction') === 'forward'
794
- ? 'start'
795
- : 'end',
861
+ type: direction === 'forward' ? 'start' : 'end',
862
+ times: times ? parseInt(times) : undefined,
796
863
  });
864
+ }
797
865
  const ending = barline.querySelector('ending');
798
866
  if (ending) {
799
867
  const type = ending.getAttribute('type');
800
- const number = parseInt(ending.getAttribute('number') || '1');
868
+ const numberAttr = ending.getAttribute('number') || '1';
869
+ // Handle "1,2" or "1 2"
870
+ const numbers = numberAttr
871
+ .split(/[,\s]+/)
872
+ .map((n) => parseInt(n))
873
+ .filter((n) => !isNaN(n));
801
874
  if (type === 'start')
802
- volta = { type: 'start', number };
875
+ volta = { type: 'start', numbers };
803
876
  else if (type === 'stop')
804
- volta = { type: 'stop', number };
877
+ volta = { type: 'stop', numbers };
805
878
  else if (type === 'discontinue')
806
- volta = { type: 'both', number };
879
+ volta = { type: 'both', numbers };
807
880
  }
808
881
  });
809
882
  return { repeats, volta, barlineStyle };
@@ -876,12 +949,12 @@ export class MusicXMLParser {
876
949
  };
877
950
  return map[type] ?? Duration.Quarter;
878
951
  }
879
- mapClef(sign, line = 0) {
952
+ mapClef(sign, line = 0, octaveChange = 0) {
880
953
  switch (sign.toUpperCase()) {
881
954
  case 'G':
882
- return Clef.Treble;
955
+ return octaveChange === -1 ? Clef.Treble8vaBassa : Clef.Treble;
883
956
  case 'F':
884
- return Clef.Bass;
957
+ return octaveChange === -1 ? Clef.Bass8vaBassa : Clef.Bass;
885
958
  case 'C':
886
959
  return line === 4 ? Clef.Tenor : Clef.Alto;
887
960
  case 'PERCUSSION':
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
+ }
@@ -1,6 +1,6 @@
1
1
  import { Note, NoteJSON } from './Note';
2
2
  import { NoteSet, NoteSetJSON } from './NoteSet';
3
- import { TimeSignature, KeySignature, Duration, Repeat, Volta, Clef, Tempo, BarlineStyle } from './types';
3
+ import { BarlineStyle, Clef, Duration, KeySignature, Repeat, Tempo, TimeSignature, Volta } from './types';
4
4
  /**
5
5
  * Represents a single measure containing note sets, potentially in multiple voices.
6
6
  */
@@ -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;
@@ -1,6 +1,6 @@
1
1
  import { Note } from './Note';
2
2
  import { NoteSet } from './NoteSet';
3
- import { DURATION_VALUES, decomposeDuration, BarlineStyle, } from './types';
3
+ import { BarlineStyle, DURATION_VALUES, 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)
@@ -1,5 +1,5 @@
1
- import { Duration, Accidental, Articulation, Dynamic, Slur, Tuplet, Hairpin, Glissando, Arpeggio, Ottava, Pedal, Ornament, FretboardDiagram, NoteheadShape, Bowing, Lyric, StemDirection } from './types';
2
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
3
  /**
4
4
  * Represents a single note or rest in a measure.
5
5
  */
@@ -1,5 +1,5 @@
1
- import { Duration, Accidental, DURATION_VALUES, } from './types';
2
1
  import { Pitch } from './Pitch';
2
+ import { Accidental, DURATION_VALUES, Duration, } from './types';
3
3
  /**
4
4
  * Represents a single note or rest in a measure.
5
5
  */
@@ -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: (string | 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?: string): 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[];
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 allLyrics(): Lyric[];
71
+ get lyrics(): (string | Lyric)[] | undefined;
70
72
  get lyric(): string | 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
76
  get hammerOn(): "start" | "stop" | undefined;
75
77
  get pullOff(): "start" | "stop" | undefined;
76
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[];