@scorelabs/core 1.0.1
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 +36 -0
- package/dist/importers/MusicXMLParser.js +610 -0
- package/dist/importers/index.d.ts +1 -0
- package/dist/importers/index.js +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/models/Instrument.d.ts +26 -0
- package/dist/models/Instrument.js +56 -0
- package/dist/models/Measure.d.ts +65 -0
- package/dist/models/Measure.js +291 -0
- package/dist/models/Note.d.ts +116 -0
- package/dist/models/Note.js +259 -0
- package/dist/models/NoteSet.d.ts +73 -0
- package/dist/models/NoteSet.js +184 -0
- package/dist/models/Part.d.ts +36 -0
- package/dist/models/Part.js +89 -0
- package/dist/models/Pitch.d.ts +20 -0
- package/dist/models/Pitch.js +110 -0
- package/dist/models/Score.d.ts +71 -0
- package/dist/models/Score.js +284 -0
- package/dist/models/Staff.d.ts +36 -0
- package/dist/models/Staff.js +89 -0
- package/dist/models/index.d.ts +9 -0
- package/dist/models/index.js +9 -0
- package/dist/models/types.d.ts +202 -0
- package/dist/models/types.js +205 -0
- package/package.json +34 -0
|
@@ -0,0 +1,56 @@
|
|
|
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 = {}));
|
|
22
|
+
export const PRESET_INSTRUMENTS = {
|
|
23
|
+
[InstrumentPreset.Piano]: {
|
|
24
|
+
name: 'Acoustic Grand Piano',
|
|
25
|
+
midiProgram: 0,
|
|
26
|
+
type: InstrumentType.Keyboard,
|
|
27
|
+
},
|
|
28
|
+
[InstrumentPreset.Violin]: { name: 'Violin', midiProgram: 40, type: InstrumentType.String },
|
|
29
|
+
[InstrumentPreset.Cello]: { name: 'Cello', midiProgram: 42, type: InstrumentType.String },
|
|
30
|
+
[InstrumentPreset.Guitar]: {
|
|
31
|
+
name: 'Acoustic Guitar (nylon)',
|
|
32
|
+
midiProgram: 24,
|
|
33
|
+
type: InstrumentType.String,
|
|
34
|
+
},
|
|
35
|
+
[InstrumentPreset.ElectricGuitar]: {
|
|
36
|
+
name: 'Electric Guitar (clean)',
|
|
37
|
+
midiProgram: 27,
|
|
38
|
+
type: InstrumentType.String,
|
|
39
|
+
},
|
|
40
|
+
[InstrumentPreset.Bass]: {
|
|
41
|
+
name: 'Acoustic Bass',
|
|
42
|
+
midiProgram: 32,
|
|
43
|
+
type: InstrumentType.String,
|
|
44
|
+
},
|
|
45
|
+
[InstrumentPreset.Flute]: { name: 'Flute', midiProgram: 73, type: InstrumentType.Woodwind },
|
|
46
|
+
[InstrumentPreset.Trumpet]: { name: 'Trumpet', midiProgram: 56, type: InstrumentType.Brass },
|
|
47
|
+
[InstrumentPreset.Drums]: {
|
|
48
|
+
name: 'Drum Kit',
|
|
49
|
+
midiProgram: 118,
|
|
50
|
+
type: InstrumentType.Percussion,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export function getInstrumentByProgram(program) {
|
|
54
|
+
const match = Object.values(PRESET_INSTRUMENTS).find((i) => i.midiProgram === program);
|
|
55
|
+
return match || PRESET_INSTRUMENTS[InstrumentPreset.Piano];
|
|
56
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Note, NoteJSON } from './Note';
|
|
2
|
+
import { NoteSet, NoteSetJSON } from './NoteSet';
|
|
3
|
+
import { TimeSignature, KeySignature, Duration, Repeat, Volta, Clef, Tempo, BarlineStyle } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Represents a single measure containing note sets, potentially in multiple voices.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Measure {
|
|
8
|
+
readonly voices: NoteSet[][];
|
|
9
|
+
readonly timeSignature?: TimeSignature | undefined;
|
|
10
|
+
readonly keySignature?: KeySignature | undefined;
|
|
11
|
+
readonly systemBreak: boolean;
|
|
12
|
+
readonly pageBreak: boolean;
|
|
13
|
+
readonly repeats: Repeat[];
|
|
14
|
+
readonly volta?: Volta | undefined;
|
|
15
|
+
readonly isPickup: boolean;
|
|
16
|
+
readonly clef?: Clef | undefined;
|
|
17
|
+
readonly tempo?: Tempo | undefined;
|
|
18
|
+
readonly rehearsalMark?: string | undefined;
|
|
19
|
+
readonly systemText?: string | undefined;
|
|
20
|
+
readonly barlineStyle: BarlineStyle;
|
|
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
|
+
get notes(): NoteSet[];
|
|
23
|
+
changeNoteDuration(noteIndex: number, newDuration: Duration, isDotted?: boolean, voiceIndex?: number): Measure;
|
|
24
|
+
getTotalDuration(voiceIndex?: number): number;
|
|
25
|
+
static fromJSON(data: MeasureJSON): Measure;
|
|
26
|
+
transpose(semitones: number): Measure;
|
|
27
|
+
replaceNoteSet(index: number, newNoteSet: NoteSet, voiceIndex?: number): Measure;
|
|
28
|
+
replaceNote(noteIndex: number, newNote: Note, voiceIndex?: number): Measure;
|
|
29
|
+
deleteNote(noteIndex: number, voiceIndex?: number): Measure;
|
|
30
|
+
autoBeam(timeSign: TimeSignature): Measure;
|
|
31
|
+
withTimeSignature(timeSignature?: TimeSignature): Measure;
|
|
32
|
+
withKeySignature(keySignature?: KeySignature): Measure;
|
|
33
|
+
withClef(clef?: Clef): Measure;
|
|
34
|
+
withTempo(tempo?: Tempo): Measure;
|
|
35
|
+
withSystemBreak(systemBreak: boolean): Measure;
|
|
36
|
+
withPageBreak(pageBreak: boolean): Measure;
|
|
37
|
+
withVoices(voices: NoteSet[][]): Measure;
|
|
38
|
+
withVoiceNotes(notes: Note[], voiceIndex?: number): Measure;
|
|
39
|
+
withRepeat(repeat?: Repeat): Measure;
|
|
40
|
+
withRepeats(repeats: Repeat[]): Measure;
|
|
41
|
+
withVolta(volta?: Volta): Measure;
|
|
42
|
+
withPickup(isPickup: boolean): Measure;
|
|
43
|
+
withRehearsalMark(mark?: string): Measure;
|
|
44
|
+
withSystemText(text?: string): Measure;
|
|
45
|
+
withBarlineStyle(style: BarlineStyle): Measure;
|
|
46
|
+
fillVoiceWithRests(voiceIndex: number, targetDuration: number): Measure;
|
|
47
|
+
toJSON(): MeasureJSON;
|
|
48
|
+
}
|
|
49
|
+
export interface MeasureJSON {
|
|
50
|
+
notes?: NoteJSON[];
|
|
51
|
+
voices?: NoteSetJSON[][];
|
|
52
|
+
timeSignature?: TimeSignature;
|
|
53
|
+
keySignature?: KeySignature;
|
|
54
|
+
systemBreak?: boolean;
|
|
55
|
+
pageBreak?: boolean;
|
|
56
|
+
repeat?: Repeat;
|
|
57
|
+
repeats?: Repeat[];
|
|
58
|
+
volta?: Volta;
|
|
59
|
+
isPickup?: boolean;
|
|
60
|
+
clef?: string;
|
|
61
|
+
tempo?: Tempo;
|
|
62
|
+
rehearsalMark?: string;
|
|
63
|
+
systemText?: string;
|
|
64
|
+
barlineStyle?: BarlineStyle;
|
|
65
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { Note } from './Note';
|
|
2
|
+
import { NoteSet } from './NoteSet';
|
|
3
|
+
import { DURATION_VALUES, decomposeDuration, BarlineStyle, } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Represents a single measure containing note sets, potentially in multiple voices.
|
|
6
|
+
*/
|
|
7
|
+
export class Measure {
|
|
8
|
+
voices;
|
|
9
|
+
timeSignature;
|
|
10
|
+
keySignature;
|
|
11
|
+
systemBreak;
|
|
12
|
+
pageBreak;
|
|
13
|
+
repeats;
|
|
14
|
+
volta;
|
|
15
|
+
isPickup;
|
|
16
|
+
clef;
|
|
17
|
+
tempo;
|
|
18
|
+
rehearsalMark;
|
|
19
|
+
systemText;
|
|
20
|
+
barlineStyle;
|
|
21
|
+
constructor(voices, timeSignature, keySignature, systemBreak = false, pageBreak = false, repeats = [], volta, isPickup = false, clef, tempo, rehearsalMark, systemText, barlineStyle = BarlineStyle.Regular) {
|
|
22
|
+
this.voices = voices;
|
|
23
|
+
this.timeSignature = timeSignature;
|
|
24
|
+
this.keySignature = keySignature;
|
|
25
|
+
this.systemBreak = systemBreak;
|
|
26
|
+
this.pageBreak = pageBreak;
|
|
27
|
+
this.repeats = repeats;
|
|
28
|
+
this.volta = volta;
|
|
29
|
+
this.isPickup = isPickup;
|
|
30
|
+
this.clef = clef;
|
|
31
|
+
this.tempo = tempo;
|
|
32
|
+
this.rehearsalMark = rehearsalMark;
|
|
33
|
+
this.systemText = systemText;
|
|
34
|
+
this.barlineStyle = barlineStyle;
|
|
35
|
+
if (this.voices.length === 0) {
|
|
36
|
+
this.voices = [[]];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
get notes() {
|
|
40
|
+
return this.voices[0] || [];
|
|
41
|
+
}
|
|
42
|
+
changeNoteDuration(noteIndex, newDuration, isDotted = false, voiceIndex = 0) {
|
|
43
|
+
const voice = this.voices[voiceIndex];
|
|
44
|
+
if (!voice || noteIndex < 0 || noteIndex >= voice.length)
|
|
45
|
+
return this;
|
|
46
|
+
const targetNoteSet = voice[noteIndex];
|
|
47
|
+
const oldVal = targetNoteSet.getDurationValue();
|
|
48
|
+
const base = DURATION_VALUES[newDuration];
|
|
49
|
+
const newVal = isDotted ? base * 1.5 : base;
|
|
50
|
+
if (Math.abs(newVal - oldVal) < 0.001)
|
|
51
|
+
return this;
|
|
52
|
+
const updatedVoice = [...voice];
|
|
53
|
+
if (newVal < oldVal) {
|
|
54
|
+
const gap = oldVal - newVal;
|
|
55
|
+
updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
|
|
56
|
+
const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
|
|
57
|
+
updatedVoice.splice(noteIndex + 1, 0, ...rests);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const delta = newVal - oldVal;
|
|
61
|
+
let available = 0;
|
|
62
|
+
for (let i = noteIndex + 1; i < updatedVoice.length; i++) {
|
|
63
|
+
available += updatedVoice[i].getDurationValue();
|
|
64
|
+
}
|
|
65
|
+
if (available < delta - 0.001)
|
|
66
|
+
return this;
|
|
67
|
+
updatedVoice[noteIndex] = targetNoteSet.withDuration(newDuration, isDotted);
|
|
68
|
+
let consumed = 0;
|
|
69
|
+
let removeCount = 0;
|
|
70
|
+
const replacements = [];
|
|
71
|
+
for (let i = noteIndex + 1; i < voice.length; i++) {
|
|
72
|
+
const ns = voice[i];
|
|
73
|
+
const val = ns.getDurationValue();
|
|
74
|
+
if (consumed + val <= delta + 0.001) {
|
|
75
|
+
consumed += val;
|
|
76
|
+
removeCount++;
|
|
77
|
+
if (consumed >= delta - 0.001)
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const remaining = consumed + val - delta;
|
|
82
|
+
replacements.push(...decomposeDuration(remaining).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)])));
|
|
83
|
+
removeCount++;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
updatedVoice.splice(noteIndex + 1, removeCount, ...replacements);
|
|
88
|
+
}
|
|
89
|
+
const newVoices = [...this.voices];
|
|
90
|
+
newVoices[voiceIndex] = updatedVoice;
|
|
91
|
+
return this.withVoices(newVoices);
|
|
92
|
+
}
|
|
93
|
+
getTotalDuration(voiceIndex = 0) {
|
|
94
|
+
const voice = this.voices[voiceIndex];
|
|
95
|
+
if (!voice)
|
|
96
|
+
return 0;
|
|
97
|
+
return voice.reduce((sum, ns) => sum + ns.getDurationValue(), 0);
|
|
98
|
+
}
|
|
99
|
+
static fromJSON(data) {
|
|
100
|
+
let voices;
|
|
101
|
+
if (data.voices) {
|
|
102
|
+
voices = data.voices.map((v) => v.map((n) => NoteSet.fromJSON(n)));
|
|
103
|
+
}
|
|
104
|
+
else if (data.notes) {
|
|
105
|
+
voices = [data.notes.map((n) => new NoteSet([Note.fromJSON(n)]))];
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
voices = [[]];
|
|
109
|
+
}
|
|
110
|
+
return new Measure(voices, data.timeSignature, data.keySignature, data.systemBreak ?? false, data.pageBreak ?? false, data.repeats || (data.repeat ? [data.repeat] : []), data.volta, data.isPickup ?? false, data.clef, data.tempo, data.rehearsalMark, data.systemText, data.barlineStyle ?? BarlineStyle.Regular);
|
|
111
|
+
}
|
|
112
|
+
transpose(semitones) {
|
|
113
|
+
return this.withVoices(this.voices.map((v) => v.map((ns) => ns.transpose(semitones))));
|
|
114
|
+
}
|
|
115
|
+
replaceNoteSet(index, newNoteSet, voiceIndex = 0) {
|
|
116
|
+
if (index < 0 || voiceIndex < 0 || voiceIndex >= this.voices.length)
|
|
117
|
+
return this;
|
|
118
|
+
const voice = [...this.voices[voiceIndex]];
|
|
119
|
+
if (index >= voice.length)
|
|
120
|
+
return this;
|
|
121
|
+
voice[index] = newNoteSet;
|
|
122
|
+
const newVoices = [...this.voices];
|
|
123
|
+
newVoices[voiceIndex] = voice;
|
|
124
|
+
return this.withVoices(newVoices);
|
|
125
|
+
}
|
|
126
|
+
replaceNote(noteIndex, newNote, voiceIndex = 0) {
|
|
127
|
+
return this.replaceNoteSet(noteIndex, new NoteSet([newNote]), voiceIndex);
|
|
128
|
+
}
|
|
129
|
+
deleteNote(noteIndex, voiceIndex = 0) {
|
|
130
|
+
if (voiceIndex < 0 || voiceIndex >= this.voices.length)
|
|
131
|
+
return this;
|
|
132
|
+
const voice = [...this.voices[voiceIndex]];
|
|
133
|
+
if (noteIndex < 0 || noteIndex >= voice.length)
|
|
134
|
+
return this;
|
|
135
|
+
voice.splice(noteIndex, 1);
|
|
136
|
+
const newVoices = [...this.voices];
|
|
137
|
+
newVoices[voiceIndex] = voice;
|
|
138
|
+
return this.withVoices(newVoices);
|
|
139
|
+
}
|
|
140
|
+
autoBeam(timeSign) {
|
|
141
|
+
const newVoices = this.voices.map((voice) => {
|
|
142
|
+
let currentOffset = 0;
|
|
143
|
+
let lastGroupId = undefined;
|
|
144
|
+
let lastGroupStartTime = 0;
|
|
145
|
+
let lastGroupHasSixteenths = false;
|
|
146
|
+
const tempNoteSets = [];
|
|
147
|
+
for (const ns of voice) {
|
|
148
|
+
const noteDuration = ns.getDurationValue();
|
|
149
|
+
let groupToAssign = undefined;
|
|
150
|
+
if (ns.isBeamable()) {
|
|
151
|
+
const isSixteenth = noteDuration < 0.5;
|
|
152
|
+
let currentBeatLength = 4 / timeSign.beatType;
|
|
153
|
+
if (timeSign.beatType === 8 && timeSign.beats % 3 === 0) {
|
|
154
|
+
currentBeatLength = 1.5;
|
|
155
|
+
}
|
|
156
|
+
else if (timeSign.beatType === 4 && (timeSign.beats === 4 || timeSign.beats === 2)) {
|
|
157
|
+
currentBeatLength = (lastGroupHasSixteenths || isSixteenth) ? 1.0 : 2.0;
|
|
158
|
+
}
|
|
159
|
+
else if (timeSign.beatType === 2 && timeSign.beats === 2) {
|
|
160
|
+
currentBeatLength = (lastGroupHasSixteenths || isSixteenth) ? 1.0 : 2.0;
|
|
161
|
+
}
|
|
162
|
+
const currentBeatId = Math.floor(currentOffset / currentBeatLength + 0.001);
|
|
163
|
+
const endOffset = currentOffset + noteDuration;
|
|
164
|
+
const crossesBeat = Math.floor(endOffset / currentBeatLength - 0.001) !== currentBeatId;
|
|
165
|
+
const sameBeatAsLast = lastGroupId !== undefined &&
|
|
166
|
+
Math.floor(lastGroupStartTime / currentBeatLength + 0.001) === currentBeatId;
|
|
167
|
+
if (sameBeatAsLast && !crossesBeat) {
|
|
168
|
+
groupToAssign = lastGroupId;
|
|
169
|
+
if (isSixteenth)
|
|
170
|
+
lastGroupHasSixteenths = true;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
lastGroupId = Math.floor(Math.random() * 1000000) + 1;
|
|
174
|
+
lastGroupStartTime = currentOffset;
|
|
175
|
+
lastGroupHasSixteenths = isSixteenth;
|
|
176
|
+
groupToAssign = lastGroupId;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
lastGroupId = undefined;
|
|
181
|
+
lastGroupHasSixteenths = false;
|
|
182
|
+
}
|
|
183
|
+
tempNoteSets.push(ns.withBeamGroup(groupToAssign));
|
|
184
|
+
currentOffset += noteDuration;
|
|
185
|
+
}
|
|
186
|
+
const groupCounts = new Map();
|
|
187
|
+
for (const ns of tempNoteSets) {
|
|
188
|
+
if (ns.beamGroup !== undefined) {
|
|
189
|
+
groupCounts.set(ns.beamGroup, (groupCounts.get(ns.beamGroup) || 0) + 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return tempNoteSets.map((ns) => {
|
|
193
|
+
if (ns.beamGroup !== undefined && (groupCounts.get(ns.beamGroup) || 0) < 2) {
|
|
194
|
+
return ns.withBeamGroup(undefined);
|
|
195
|
+
}
|
|
196
|
+
return ns;
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
return this.withVoices(newVoices);
|
|
200
|
+
}
|
|
201
|
+
withTimeSignature(timeSignature) {
|
|
202
|
+
return new Measure(this.voices, timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
203
|
+
}
|
|
204
|
+
withKeySignature(keySignature) {
|
|
205
|
+
return new Measure(this.voices, this.timeSignature, keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
206
|
+
}
|
|
207
|
+
withClef(clef) {
|
|
208
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
209
|
+
}
|
|
210
|
+
withTempo(tempo) {
|
|
211
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
212
|
+
}
|
|
213
|
+
withSystemBreak(systemBreak) {
|
|
214
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
215
|
+
}
|
|
216
|
+
withPageBreak(pageBreak) {
|
|
217
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
218
|
+
}
|
|
219
|
+
withVoices(voices) {
|
|
220
|
+
return new Measure(voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
221
|
+
}
|
|
222
|
+
withVoiceNotes(notes, voiceIndex = 0) {
|
|
223
|
+
const newVoices = [...this.voices];
|
|
224
|
+
while (newVoices.length <= voiceIndex)
|
|
225
|
+
newVoices.push([]);
|
|
226
|
+
newVoices[voiceIndex] = notes.map((n) => new NoteSet([n]));
|
|
227
|
+
return this.withVoices(newVoices);
|
|
228
|
+
}
|
|
229
|
+
withRepeat(repeat) {
|
|
230
|
+
if (!repeat)
|
|
231
|
+
return this.withRepeats([]);
|
|
232
|
+
const existing = this.repeats.find((r) => r.type === repeat.type);
|
|
233
|
+
let newRepeats;
|
|
234
|
+
if (existing) {
|
|
235
|
+
newRepeats = this.repeats.filter((r) => r.type !== repeat.type);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
newRepeats = [...this.repeats, repeat];
|
|
239
|
+
}
|
|
240
|
+
return this.withRepeats(newRepeats);
|
|
241
|
+
}
|
|
242
|
+
withRepeats(repeats) {
|
|
243
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
244
|
+
}
|
|
245
|
+
withVolta(volta) {
|
|
246
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
247
|
+
}
|
|
248
|
+
withPickup(isPickup) {
|
|
249
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, this.barlineStyle);
|
|
250
|
+
}
|
|
251
|
+
withRehearsalMark(mark) {
|
|
252
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, mark, this.systemText, this.barlineStyle);
|
|
253
|
+
}
|
|
254
|
+
withSystemText(text) {
|
|
255
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, text, this.barlineStyle);
|
|
256
|
+
}
|
|
257
|
+
withBarlineStyle(style) {
|
|
258
|
+
return new Measure(this.voices, this.timeSignature, this.keySignature, this.systemBreak, this.pageBreak, this.repeats, this.volta, this.isPickup, this.clef, this.tempo, this.rehearsalMark, this.systemText, style);
|
|
259
|
+
}
|
|
260
|
+
fillVoiceWithRests(voiceIndex, targetDuration) {
|
|
261
|
+
const newVoices = [...this.voices];
|
|
262
|
+
while (newVoices.length <= voiceIndex) {
|
|
263
|
+
newVoices.push([]);
|
|
264
|
+
}
|
|
265
|
+
const currentVoice = newVoices[voiceIndex];
|
|
266
|
+
const currentDuration = currentVoice.reduce((sum, ns) => sum + ns.getDurationValue(), 0);
|
|
267
|
+
if (currentDuration < targetDuration - 0.001) {
|
|
268
|
+
const gap = targetDuration - currentDuration;
|
|
269
|
+
const rests = decomposeDuration(gap).map((d) => new NoteSet([new Note(d.duration, undefined, true, d.isDotted)]));
|
|
270
|
+
newVoices[voiceIndex] = [...currentVoice, ...rests];
|
|
271
|
+
}
|
|
272
|
+
return this.withVoices(newVoices);
|
|
273
|
+
}
|
|
274
|
+
toJSON() {
|
|
275
|
+
return {
|
|
276
|
+
voices: this.voices.map((v) => v.map((ns) => ns.toJSON())),
|
|
277
|
+
timeSignature: this.timeSignature,
|
|
278
|
+
keySignature: this.keySignature,
|
|
279
|
+
systemBreak: this.systemBreak || undefined,
|
|
280
|
+
pageBreak: this.pageBreak || undefined,
|
|
281
|
+
repeats: this.repeats,
|
|
282
|
+
volta: this.volta,
|
|
283
|
+
isPickup: this.isPickup || undefined,
|
|
284
|
+
clef: this.clef,
|
|
285
|
+
tempo: this.tempo,
|
|
286
|
+
rehearsalMark: this.rehearsalMark,
|
|
287
|
+
systemText: this.systemText,
|
|
288
|
+
barlineStyle: this.barlineStyle,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Duration, Accidental, Articulation, Dynamic, Slur, Tuplet, Hairpin, Glissando, Arpeggio, Ottava, Pedal, Ornament, FretboardDiagram, NoteheadShape, Bowing } from './types';
|
|
2
|
+
import { Pitch } from './Pitch';
|
|
3
|
+
/**
|
|
4
|
+
* Represents a single note or rest in a measure.
|
|
5
|
+
*/
|
|
6
|
+
export declare class Note {
|
|
7
|
+
readonly duration: Duration;
|
|
8
|
+
readonly pitch?: Pitch | undefined;
|
|
9
|
+
readonly isRest: boolean;
|
|
10
|
+
readonly isDotted: boolean;
|
|
11
|
+
readonly accidental?: Accidental | undefined;
|
|
12
|
+
readonly beamGroup?: number | undefined;
|
|
13
|
+
readonly articulation?: Articulation | undefined;
|
|
14
|
+
readonly dynamic?: Dynamic | undefined;
|
|
15
|
+
readonly tie?: boolean | undefined;
|
|
16
|
+
readonly slur?: Slur | undefined;
|
|
17
|
+
readonly tuplet?: Tuplet | undefined;
|
|
18
|
+
readonly hairpin?: Hairpin | undefined;
|
|
19
|
+
readonly isGrace: boolean;
|
|
20
|
+
readonly lyric?: string | undefined;
|
|
21
|
+
readonly chord?: string | undefined;
|
|
22
|
+
readonly glissando?: Glissando | undefined;
|
|
23
|
+
readonly arpeggio?: Arpeggio | undefined;
|
|
24
|
+
readonly ottava?: Ottava | undefined;
|
|
25
|
+
readonly pedal?: Pedal | undefined;
|
|
26
|
+
readonly ornament?: Ornament | undefined;
|
|
27
|
+
readonly fret?: number | undefined;
|
|
28
|
+
readonly string?: number | undefined;
|
|
29
|
+
readonly fretboardDiagram?: FretboardDiagram | undefined;
|
|
30
|
+
readonly lyrics?: string[] | undefined;
|
|
31
|
+
readonly staffText?: string | undefined;
|
|
32
|
+
readonly color?: string | undefined;
|
|
33
|
+
readonly notehead?: NoteheadShape | undefined;
|
|
34
|
+
readonly bowing?: Bowing | undefined;
|
|
35
|
+
readonly fingering?: number | undefined;
|
|
36
|
+
constructor(duration: Duration, pitch?: Pitch | undefined, // undefined for rests
|
|
37
|
+
isRest?: boolean, isDotted?: boolean, accidental?: Accidental | undefined, beamGroup?: number | undefined, // Group ID for beamed notes
|
|
38
|
+
articulation?: Articulation | undefined, dynamic?: Dynamic | undefined, tie?: boolean | undefined, slur?: Slur | undefined, tuplet?: Tuplet | undefined, hairpin?: Hairpin | undefined, isGrace?: boolean, lyric?: string | undefined, chord?: string | undefined, glissando?: Glissando | undefined, arpeggio?: Arpeggio | undefined, ottava?: Ottava | undefined, pedal?: Pedal | undefined, ornament?: Ornament | undefined, fret?: number | undefined, string?: number | undefined, fretboardDiagram?: FretboardDiagram | undefined, lyrics?: string[] | undefined, staffText?: string | undefined, color?: string | undefined, notehead?: NoteheadShape | undefined, bowing?: Bowing | undefined, fingering?: number | undefined);
|
|
39
|
+
/**
|
|
40
|
+
* Get the duration value (quarter note = 1)
|
|
41
|
+
*/
|
|
42
|
+
getDurationValue(): number;
|
|
43
|
+
/**
|
|
44
|
+
* Check if this note should be beamed (eighth or shorter)
|
|
45
|
+
*/
|
|
46
|
+
isBeamable(): boolean;
|
|
47
|
+
withGrace(isGrace: boolean): Note;
|
|
48
|
+
withChord(chord?: string): Note;
|
|
49
|
+
transpose(semitones: number): Note;
|
|
50
|
+
transposeOctave(octaves: number): Note;
|
|
51
|
+
transposeDiatonic(steps: number): Note;
|
|
52
|
+
toggleEnharmonic(): Note;
|
|
53
|
+
withPitch(pitch: Pitch): Note;
|
|
54
|
+
withArticulation(articulation?: Articulation): Note;
|
|
55
|
+
withDynamic(dynamic?: Dynamic): Note;
|
|
56
|
+
withTie(tie?: boolean): Note;
|
|
57
|
+
withSlur(slur?: Slur): Note;
|
|
58
|
+
withTuplet(tuplet?: Tuplet): Note;
|
|
59
|
+
withLyric(lyric?: string): Note;
|
|
60
|
+
withLyrics(lyrics: string[]): Note;
|
|
61
|
+
withHairpin(hairpin?: Hairpin): Note;
|
|
62
|
+
withAccidental(accidental?: Accidental): Note;
|
|
63
|
+
withDuration(duration: Duration, isDotted?: boolean): Note;
|
|
64
|
+
withRest(isRest: boolean): Note;
|
|
65
|
+
withFretboardDiagram(diagram?: FretboardDiagram): Note;
|
|
66
|
+
withBeamGroup(beamGroup?: number): Note;
|
|
67
|
+
withGlissando(glissando?: Glissando): Note;
|
|
68
|
+
withArpeggio(arpeggio?: Arpeggio): Note;
|
|
69
|
+
withOttava(ottava?: Ottava): Note;
|
|
70
|
+
withPedal(pedal?: Pedal): Note;
|
|
71
|
+
withOrnament(ornament?: Ornament): Note;
|
|
72
|
+
withTab(fret: number, string: number): Note;
|
|
73
|
+
withStaffText(text?: string): Note;
|
|
74
|
+
withColor(color?: string): Note;
|
|
75
|
+
withNotehead(notehead?: NoteheadShape): Note;
|
|
76
|
+
withBowing(bowing?: Bowing): Note;
|
|
77
|
+
withFingering(fingering?: number): Note;
|
|
78
|
+
toJSON(): NoteJSON;
|
|
79
|
+
static fromJSON(data: NoteJSON): Note;
|
|
80
|
+
}
|
|
81
|
+
export interface NoteJSON {
|
|
82
|
+
duration: string;
|
|
83
|
+
pitch?: {
|
|
84
|
+
midiNumber: number;
|
|
85
|
+
step: number;
|
|
86
|
+
alter: number;
|
|
87
|
+
octave: number;
|
|
88
|
+
};
|
|
89
|
+
isRest?: boolean;
|
|
90
|
+
isDotted?: boolean;
|
|
91
|
+
accidental?: string;
|
|
92
|
+
beamGroup?: number;
|
|
93
|
+
articulation?: string;
|
|
94
|
+
dynamic?: string;
|
|
95
|
+
tie?: boolean;
|
|
96
|
+
slur?: Slur;
|
|
97
|
+
tuplet?: Tuplet;
|
|
98
|
+
hairpin?: Hairpin;
|
|
99
|
+
isGrace?: boolean;
|
|
100
|
+
lyric?: string;
|
|
101
|
+
chord?: string;
|
|
102
|
+
glissando?: Glissando;
|
|
103
|
+
arpeggio?: Arpeggio;
|
|
104
|
+
ottava?: Ottava;
|
|
105
|
+
pedal?: Pedal;
|
|
106
|
+
ornament?: Ornament;
|
|
107
|
+
fret?: number;
|
|
108
|
+
string?: number;
|
|
109
|
+
fretboardDiagram?: FretboardDiagram;
|
|
110
|
+
lyrics?: string[];
|
|
111
|
+
staffText?: string;
|
|
112
|
+
color?: string;
|
|
113
|
+
notehead?: string;
|
|
114
|
+
bowing?: string;
|
|
115
|
+
fingering?: number;
|
|
116
|
+
}
|