@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.
- package/dist/importers/MusicXMLParser.d.ts +7 -2
- package/dist/importers/MusicXMLParser.js +261 -103
- 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 +5 -1
- package/dist/models/Measure.js +132 -19
- package/dist/models/Note.d.ts +7 -7
- package/dist/models/Note.js +2 -2
- package/dist/models/NoteSet.d.ts +44 -42
- package/dist/models/NoteSet.js +5 -2
- 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 +20 -5
- package/dist/models/Score.js +190 -5
- 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/User.d.ts +21 -0
- package/dist/types/User.js +7 -0
- package/dist/types/index.d.ts +27 -0
- package/dist/types/index.js +27 -0
- 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
|
-
|
|
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
|
@@ -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
|
|
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 {
|
package/dist/models/Measure.js
CHANGED
|
@@ -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
|
-
|
|
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]
|
|
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] =
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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 (
|
|
177
|
-
|
|
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 /
|
|
251
|
+
const currentBeatId = Math.floor(currentOffset / beamBeatLength + 0.001);
|
|
180
252
|
const endOffset = currentOffset + noteDuration;
|
|
181
|
-
const crossesBeat = Math.floor(endOffset /
|
|
253
|
+
const crossesBeat = Math.floor(endOffset / beamBeatLength - 0.001) !== currentBeatId;
|
|
182
254
|
const sameBeatAsLast = lastGroupId !== undefined &&
|
|
183
|
-
Math.floor(lastGroupStartTime /
|
|
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())),
|
package/dist/models/Note.d.ts
CHANGED
|
@@ -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?:
|
|
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?:
|
|
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?:
|
|
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?:
|
|
74
|
-
withLyrics(lyrics:
|
|
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?:
|
|
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?:
|
|
130
|
+
lyrics?: Lyric[];
|
|
131
131
|
staffText?: string;
|
|
132
132
|
color?: string;
|
|
133
133
|
notehead?: string;
|
package/dist/models/Note.js
CHANGED
|
@@ -81,10 +81,10 @@ export class Note {
|
|
|
81
81
|
*/
|
|
82
82
|
get allLyrics() {
|
|
83
83
|
if (this.lyrics) {
|
|
84
|
-
return this.lyrics
|
|
84
|
+
return this.lyrics;
|
|
85
85
|
}
|
|
86
86
|
if (this.lyric) {
|
|
87
|
-
return [
|
|
87
|
+
return [this.lyric];
|
|
88
88
|
}
|
|
89
89
|
return [];
|
|
90
90
|
}
|
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:
|
|
17
|
-
withTuplet(tuplet?:
|
|
18
|
-
withPitch(pitch:
|
|
19
|
-
withAccidental(accidental
|
|
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?:
|
|
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?: Lyric): 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[] | 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():
|
|
68
|
+
get bowing(): Bowing | undefined;
|
|
67
69
|
get fingering(): number | undefined;
|
|
68
|
-
get allLyrics():
|
|
69
|
-
get lyrics():
|
|
70
|
-
get lyric():
|
|
70
|
+
get allLyrics(): Lyric[];
|
|
71
|
+
get lyrics(): Lyric[] | undefined;
|
|
72
|
+
get lyric(): Lyric | undefined;
|
|
71
73
|
get chord(): string | undefined;
|
|
72
|
-
get fretboardDiagram():
|
|
74
|
+
get fretboardDiagram(): FretboardDiagram | undefined;
|
|
73
75
|
get staffText(): string | undefined;
|
|
74
|
-
get hammerOn():
|
|
75
|
-
get pullOff():
|
|
76
|
-
get palmMute():
|
|
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():
|
|
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[];
|