@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.
- package/README.md +1 -1
- package/dist/importers/MusicXMLParser.d.ts +2 -1
- package/dist/importers/MusicXMLParser.js +102 -29
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/models/Instrument.d.ts +8 -19
- package/dist/models/Instrument.js +77 -23
- package/dist/models/Measure.d.ts +3 -1
- package/dist/models/Measure.js +7 -1
- package/dist/models/Note.d.ts +1 -1
- package/dist/models/Note.js +1 -1
- package/dist/models/NoteSet.d.ts +40 -38
- package/dist/models/NoteSet.js +5 -2
- package/dist/models/Part.d.ts +2 -2
- package/dist/models/Part.js +1 -1
- package/dist/models/Pitch.js +4 -0
- package/dist/models/PreMeasure.d.ts +24 -0
- package/dist/models/PreMeasure.js +30 -0
- package/dist/models/Score.d.ts +18 -3
- package/dist/models/Score.js +196 -8
- package/dist/models/Staff.d.ts +2 -2
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +1 -0
- package/dist/models/types.d.ts +2 -213
- package/dist/models/types.js +2 -213
- package/dist/types/Accidental.d.ts +7 -0
- package/dist/types/Accidental.js +8 -0
- package/dist/types/Arpeggio.d.ts +5 -0
- package/dist/types/Arpeggio.js +6 -0
- package/dist/types/Articulation.d.ts +10 -0
- package/dist/types/Articulation.js +11 -0
- package/dist/types/BarlineStyle.d.ts +9 -0
- package/dist/types/BarlineStyle.js +10 -0
- package/dist/types/Bowing.d.ts +4 -0
- package/dist/types/Bowing.js +5 -0
- package/dist/types/Clef.d.ts +10 -0
- package/dist/types/Clef.js +11 -0
- package/dist/types/Duration.d.ts +20 -0
- package/dist/types/Duration.js +60 -0
- package/dist/types/Dynamic.d.ts +12 -0
- package/dist/types/Dynamic.js +13 -0
- package/dist/types/Fretboard.d.ts +19 -0
- package/dist/types/Fretboard.js +1 -0
- package/dist/types/Genre.d.ts +27 -0
- package/dist/types/Genre.js +28 -0
- package/dist/types/Glissando.d.ts +8 -0
- package/dist/types/Glissando.js +5 -0
- package/dist/types/Hairpin.d.ts +10 -0
- package/dist/types/Hairpin.js +11 -0
- package/dist/types/InstrumentPreset.d.ts +11 -0
- package/dist/types/InstrumentPreset.js +12 -0
- package/dist/types/InstrumentType.d.ts +8 -0
- package/dist/types/InstrumentType.js +9 -0
- package/dist/types/KeySignature.d.ts +3 -0
- package/dist/types/KeySignature.js +1 -0
- package/dist/types/Lyric.d.ts +11 -0
- package/dist/types/Lyric.js +7 -0
- package/dist/types/NoteheadShape.d.ts +8 -0
- package/dist/types/NoteheadShape.js +9 -0
- package/dist/types/Ornament.d.ts +8 -0
- package/dist/types/Ornament.js +9 -0
- package/dist/types/Ottava.d.ts +10 -0
- package/dist/types/Ottava.js +7 -0
- package/dist/types/Pedal.d.ts +4 -0
- package/dist/types/Pedal.js +1 -0
- package/dist/types/Repeat.d.ts +8 -0
- package/dist/types/Repeat.js +1 -0
- package/dist/types/Slur.d.ts +4 -0
- package/dist/types/Slur.js +1 -0
- package/dist/types/StemDirection.d.ts +4 -0
- package/dist/types/StemDirection.js +5 -0
- package/dist/types/Tempo.d.ts +8 -0
- package/dist/types/Tempo.js +1 -0
- package/dist/types/TimeSignature.d.ts +6 -0
- package/dist/types/TimeSignature.js +3 -0
- package/dist/types/Tuplet.d.ts +5 -0
- package/dist/types/Tuplet.js +1 -0
- package/dist/types/index.d.ts +26 -0
- package/dist/types/index.js +26 -0
- package/package.json +5 -4
- package/dist/utils/tier.d.ts +0 -36
- 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
|
|
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?:
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
45
|
+
return scoreXml;
|
|
43
46
|
}
|
|
44
47
|
else {
|
|
45
48
|
const decoder = new TextDecoder();
|
|
46
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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:
|
|
794
|
-
|
|
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
|
|
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',
|
|
875
|
+
volta = { type: 'start', numbers };
|
|
803
876
|
else if (type === 'stop')
|
|
804
|
-
volta = { type: 'stop',
|
|
877
|
+
volta = { type: 'stop', numbers };
|
|
805
878
|
else if (type === 'discontinue')
|
|
806
|
-
volta = { type: 'both',
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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]: {
|
|
29
|
-
|
|
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
|
+
}
|
package/dist/models/Measure.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Note, NoteJSON } from './Note';
|
|
2
2
|
import { NoteSet, NoteSetJSON } from './NoteSet';
|
|
3
|
-
import {
|
|
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;
|
package/dist/models/Measure.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Note } from './Note';
|
|
2
2
|
import { NoteSet } from './NoteSet';
|
|
3
|
-
import { DURATION_VALUES, decomposeDuration,
|
|
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)
|
package/dist/models/Note.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/models/Note.js
CHANGED
package/dist/models/NoteSet.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Note, NoteJSON } from './Note';
|
|
2
|
-
import {
|
|
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():
|
|
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 |
|
|
17
|
-
withTuplet(tuplet?:
|
|
18
|
-
withPitch(pitch:
|
|
19
|
-
withAccidental(accidental
|
|
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?:
|
|
30
|
-
withArticulation(articulation?:
|
|
31
|
-
withOrnament(ornament?:
|
|
32
|
-
withDynamic(dynamic?:
|
|
33
|
-
withHairpin(hairpin?:
|
|
34
|
-
withGlissando(glissando?:
|
|
35
|
-
withArpeggio(arpeggio?:
|
|
36
|
-
withOttava(ottava?:
|
|
37
|
-
withPedal(pedal?:
|
|
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
|
|
40
|
-
withBowing(bowing?:
|
|
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?:
|
|
47
|
-
withLyric(lyric?:
|
|
47
|
+
withStemDirection(dir?: StemDirection): NoteSet;
|
|
48
|
+
withLyric(lyric?: string): NoteSet;
|
|
48
49
|
withStaffText(text?: string): NoteSet;
|
|
49
|
-
withFretboardDiagram(diagram?:
|
|
50
|
-
get pitch():
|
|
51
|
-
get accidental():
|
|
50
|
+
withFretboardDiagram(diagram?: FretboardDiagram): NoteSet;
|
|
51
|
+
get pitch(): Pitch | undefined;
|
|
52
|
+
get accidental(): Accidental | undefined;
|
|
52
53
|
get tie(): boolean | undefined;
|
|
53
|
-
get slur():
|
|
54
|
-
get
|
|
55
|
-
get
|
|
56
|
-
get
|
|
57
|
-
get
|
|
58
|
-
get
|
|
59
|
-
get
|
|
60
|
-
get
|
|
61
|
-
get
|
|
62
|
-
get
|
|
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():
|
|
68
|
+
get bowing(): Bowing | undefined;
|
|
67
69
|
get fingering(): number | undefined;
|
|
68
|
-
get allLyrics():
|
|
69
|
-
get lyrics(): (string |
|
|
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():
|
|
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():
|
|
80
|
+
get stemDirection(): StemDirection | undefined;
|
|
79
81
|
toJSON(): NoteSetJSON;
|
|
80
|
-
static fromJSON(data:
|
|
82
|
+
static fromJSON(data: Partial<NoteSetJSON> | NoteJSON | NoteJSON[]): NoteSet;
|
|
81
83
|
}
|
|
82
84
|
export interface NoteSetJSON {
|
|
83
85
|
notes: NoteJSON[];
|