@scorelabs/core 1.0.11 → 1.1.0
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 +2 -0
- package/dist/importers/MusicXMLParser.js +518 -71
- package/dist/models/Measure.d.ts +19 -2
- package/dist/models/Measure.js +79 -27
- package/dist/models/Note.d.ts +35 -5
- package/dist/models/Note.js +97 -42
- package/dist/models/NoteSet.d.ts +11 -2
- package/dist/models/NoteSet.js +33 -2
- package/dist/models/Part.d.ts +1 -1
- package/dist/models/Part.js +2 -2
- package/dist/models/Score.d.ts +5 -5
- package/dist/models/Score.js +144 -121
- package/dist/models/Staff.d.ts +1 -1
- package/dist/models/Staff.js +2 -2
- package/dist/types/AccidentalDisplay.d.ts +6 -0
- package/dist/types/AccidentalDisplay.js +1 -0
- package/dist/types/Arpeggio.d.ts +2 -1
- package/dist/types/Arpeggio.js +1 -0
- package/dist/types/Articulation.d.ts +3 -1
- package/dist/types/Articulation.js +2 -0
- package/dist/types/Beam.d.ts +4 -0
- package/dist/types/Beam.js +1 -0
- package/dist/types/Duration.d.ts +1 -1
- package/dist/types/Duration.js +14 -14
- package/dist/types/Grace.d.ts +7 -0
- package/dist/types/Grace.js +1 -0
- package/dist/types/Hairpin.d.ts +2 -1
- package/dist/types/Language.d.ts +6 -0
- package/dist/types/Language.js +7 -0
- package/dist/types/Lyric.d.ts +2 -0
- package/dist/types/Ornament.d.ts +23 -1
- package/dist/types/Ornament.js +9 -0
- package/dist/types/Pedal.d.ts +3 -2
- package/dist/types/Placement.d.ts +7 -0
- package/dist/types/Placement.js +1 -0
- package/dist/types/Repeat.d.ts +1 -0
- package/dist/types/RestDisplay.d.ts +6 -0
- package/dist/types/RestDisplay.js +1 -0
- package/dist/types/Slur.d.ts +1 -0
- package/dist/types/Technical.d.ts +20 -0
- package/dist/types/Technical.js +5 -0
- package/dist/types/Tempo.d.ts +0 -1
- package/dist/types/Tie.d.ts +4 -0
- package/dist/types/Tie.js +1 -0
- package/dist/types/Tuplet.d.ts +6 -0
- package/dist/types/User.d.ts +13 -7
- package/dist/types/User.js +9 -7
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.js +8 -0
- package/package.json +2 -1
package/dist/models/Score.js
CHANGED
|
@@ -12,7 +12,6 @@ export class Score {
|
|
|
12
12
|
parts;
|
|
13
13
|
bpm;
|
|
14
14
|
tempoDuration;
|
|
15
|
-
tempoIsDotted;
|
|
16
15
|
copyright;
|
|
17
16
|
lyricist;
|
|
18
17
|
swing;
|
|
@@ -20,7 +19,7 @@ export class Score {
|
|
|
20
19
|
genre;
|
|
21
20
|
tempoText;
|
|
22
21
|
tempoDotCount;
|
|
23
|
-
constructor(title, composer, timeSignature, keySignature, parts, bpm = 120, tempoDuration = Duration.Quarter,
|
|
22
|
+
constructor(title, composer, timeSignature, keySignature, parts, bpm = 120, tempoDuration = Duration.Quarter, copyright = '', lyricist = '', swing = false, subtitle = '', genre = Genre.Unknown, tempoText = '', tempoDotCount = 0) {
|
|
24
23
|
this.title = title;
|
|
25
24
|
this.composer = composer;
|
|
26
25
|
this.timeSignature = timeSignature;
|
|
@@ -28,7 +27,6 @@ export class Score {
|
|
|
28
27
|
this.parts = parts;
|
|
29
28
|
this.bpm = bpm;
|
|
30
29
|
this.tempoDuration = tempoDuration;
|
|
31
|
-
this.tempoIsDotted = tempoIsDotted;
|
|
32
30
|
this.copyright = copyright;
|
|
33
31
|
this.lyricist = lyricist;
|
|
34
32
|
this.swing = swing;
|
|
@@ -38,19 +36,19 @@ export class Score {
|
|
|
38
36
|
this.tempoDotCount = tempoDotCount;
|
|
39
37
|
}
|
|
40
38
|
withTitle(title) {
|
|
41
|
-
return new Score(title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
39
|
+
return new Score(title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
42
40
|
}
|
|
43
41
|
withComposer(composer) {
|
|
44
|
-
return new Score(this.title, composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
42
|
+
return new Score(this.title, composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
45
43
|
}
|
|
46
44
|
withSubtitle(subtitle) {
|
|
47
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
45
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
48
46
|
}
|
|
49
47
|
withGenre(genre) {
|
|
50
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
48
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, genre, this.tempoText, this.tempoDotCount);
|
|
51
49
|
}
|
|
52
50
|
withBpm(bpm) {
|
|
53
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, this.tempoDuration, this.
|
|
51
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
54
52
|
}
|
|
55
53
|
getMeasureCount() {
|
|
56
54
|
return this.parts[0]?.getMeasureCount() ?? 0;
|
|
@@ -93,7 +91,7 @@ export class Score {
|
|
|
93
91
|
}
|
|
94
92
|
static fromJSON(data) {
|
|
95
93
|
const parts = data.parts.map((p) => Part.fromJSON(p));
|
|
96
|
-
return new Score(data.title, data.composer, data.timeSignature, data.keySignature, parts, data.bpm ?? 120, data.tempoDuration ?? Duration.Quarter, data.
|
|
94
|
+
return new Score(data.title, data.composer, data.timeSignature, data.keySignature, parts, data.bpm ?? 120, data.tempoDuration ?? Duration.Quarter, data.copyright ?? '', data.lyricist ?? '', data.swing ?? false, data.subtitle ?? '', data.genre || Genre.Unknown, data.tempoText ?? '', data.tempoDotCount ?? (data.tempoIsDotted ? 1 : 0));
|
|
97
95
|
}
|
|
98
96
|
transpose(semitones) {
|
|
99
97
|
const semitoneToFifths = {
|
|
@@ -117,7 +115,7 @@ export class Score {
|
|
|
117
115
|
newFifths -= 12;
|
|
118
116
|
while (newFifths < -7)
|
|
119
117
|
newFifths += 12;
|
|
120
|
-
return new Score(this.title, this.composer, this.timeSignature, { fifths: newFifths }, this.parts.map((p) => p.transpose(semitones)), this.bpm, this.tempoDuration, this.
|
|
118
|
+
return new Score(this.title, this.composer, this.timeSignature, { fifths: newFifths }, this.parts.map((p) => p.transpose(semitones)), this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
121
119
|
}
|
|
122
120
|
replaceNote(partIndex, staffIndex, measureIndex, noteIndex, newNote, voiceIndex = 0) {
|
|
123
121
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
@@ -127,16 +125,15 @@ export class Score {
|
|
|
127
125
|
return this.replaceMeasure(partIndex, staffIndex, measureIndex, updatedMeasure, true);
|
|
128
126
|
}
|
|
129
127
|
withKeySignature(keySig) {
|
|
130
|
-
return new Score(this.title, this.composer, this.timeSignature, keySig, this.parts, this.bpm, this.tempoDuration, this.
|
|
128
|
+
return new Score(this.title, this.composer, this.timeSignature, keySig, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
131
129
|
}
|
|
132
130
|
withTimeSignature(timeSig) {
|
|
133
|
-
return new Score(this.title, this.composer, timeSig, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
131
|
+
return new Score(this.title, this.composer, timeSig, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
134
132
|
}
|
|
135
133
|
updateMeasureSignatures(measureIndex, signatures, autoBeam = true) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const currentMsr = score.parts[pIdx].staves[sIdx].measures[measureIndex];
|
|
134
|
+
const newParts = this.parts.map((part) => {
|
|
135
|
+
const newStaves = part.staves.map((staff) => {
|
|
136
|
+
const currentMsr = staff.measures[measureIndex];
|
|
140
137
|
if (currentMsr) {
|
|
141
138
|
let updatedMsr = currentMsr;
|
|
142
139
|
if (signatures.keySignature !== undefined) {
|
|
@@ -148,45 +145,51 @@ export class Score {
|
|
|
148
145
|
if (signatures.clef !== undefined) {
|
|
149
146
|
updatedMsr = updatedMsr.withClef(signatures.clef);
|
|
150
147
|
}
|
|
151
|
-
|
|
148
|
+
if (autoBeam) {
|
|
149
|
+
const ts = signatures.timeSignature ?? this.getTimeSignatureAt(measureIndex);
|
|
150
|
+
updatedMsr = updatedMsr.autoBeam(ts);
|
|
151
|
+
}
|
|
152
|
+
const newMeasures = [...staff.measures];
|
|
153
|
+
newMeasures[measureIndex] = updatedMsr;
|
|
154
|
+
return staff.withMeasures(newMeasures);
|
|
152
155
|
}
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
return staff;
|
|
157
|
+
});
|
|
158
|
+
return part.withStaves(newStaves);
|
|
159
|
+
});
|
|
160
|
+
const newScore = this.withParts(newParts);
|
|
155
161
|
if (signatures.timeSignature !== undefined) {
|
|
156
|
-
|
|
162
|
+
return newScore.reflow(measureIndex, autoBeam);
|
|
157
163
|
}
|
|
158
|
-
return
|
|
164
|
+
return newScore;
|
|
159
165
|
}
|
|
160
166
|
/**
|
|
161
167
|
* Reflows the notes starting from a specific measure index.
|
|
162
168
|
* This is useful when time signatures change and notes need to be redistributed.
|
|
163
169
|
*/
|
|
164
170
|
reflow(fromMeasureIndex, autoBeam = true) {
|
|
165
|
-
let currentScore = this;
|
|
166
171
|
const allStaves = this.getAllStaves();
|
|
167
|
-
|
|
168
|
-
currentScore = currentScore.reflowStaff(partIndex, staffIndex, fromMeasureIndex, autoBeam);
|
|
169
|
-
}
|
|
172
|
+
const scoreAfterReflow = allStaves.reduce((accScore, { partIndex, staffIndex }) => accScore.reflowStaff(partIndex, staffIndex, fromMeasureIndex, autoBeam), this);
|
|
170
173
|
// Harmonize measure count across all staves
|
|
171
|
-
const maxMeasures = Math.max(...
|
|
172
|
-
const minMeasures = Math.min(...
|
|
174
|
+
const maxMeasures = Math.max(...scoreAfterReflow.parts.flatMap((p) => p.staves.map((s) => s.measures.length)));
|
|
175
|
+
const minMeasures = Math.min(...scoreAfterReflow.parts.flatMap((p) => p.staves.map((s) => s.measures.length)));
|
|
173
176
|
if (maxMeasures !== minMeasures) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const staff = currentScore.parts[pIdx].staves[sIdx];
|
|
177
|
+
return scoreAfterReflow.parts.reduce((accScore1, part, pIdx) => {
|
|
178
|
+
return part.staves.reduce((accScore2, staff, sIdx) => {
|
|
177
179
|
if (staff.measures.length < maxMeasures) {
|
|
178
180
|
const updatedMsrs = [...staff.measures];
|
|
179
181
|
for (let i = staff.measures.length; i < maxMeasures; i++) {
|
|
180
|
-
const ts =
|
|
182
|
+
const ts = accScore2.getTimeSignatureAt(i);
|
|
181
183
|
const targetDur = ts.beats * (4 / ts.beatType);
|
|
182
184
|
updatedMsrs.push(new Measure([[]]).fillVoiceWithRests(0, targetDur));
|
|
183
185
|
}
|
|
184
|
-
|
|
186
|
+
return accScore2.replaceStaff(pIdx, sIdx, staff.withMeasures(updatedMsrs));
|
|
185
187
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
return accScore2;
|
|
189
|
+
}, accScore1);
|
|
190
|
+
}, scoreAfterReflow);
|
|
188
191
|
}
|
|
189
|
-
return
|
|
192
|
+
return scoreAfterReflow;
|
|
190
193
|
}
|
|
191
194
|
isTied(ns) {
|
|
192
195
|
return !!ns.notes[0].tie;
|
|
@@ -256,7 +259,7 @@ export class Score {
|
|
|
256
259
|
const newNoteSets = parts.map((p, idx) => {
|
|
257
260
|
const isLastOfLogical = Math.abs(toTake - logicalNote.duration) < 0.001 && idx === parts.length - 1;
|
|
258
261
|
return logicalNote.ns
|
|
259
|
-
.withDuration(p.duration, p.
|
|
262
|
+
.withDuration(p.duration, p.dotCount)
|
|
260
263
|
.withTie(isLastOfLogical ? !!logicalNote.ns.notes[0].tie : true);
|
|
261
264
|
});
|
|
262
265
|
msr = msr.withVoices(msr.voices.map((v, i) => (i === vIdx ? [...v, ...newNoteSets] : v)));
|
|
@@ -300,7 +303,7 @@ export class Score {
|
|
|
300
303
|
}
|
|
301
304
|
const newParts = [...this.parts];
|
|
302
305
|
newParts[partIndex] = newParts[partIndex].replaceMeasure(staffIndex, measureIndex, measureToUse);
|
|
303
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.
|
|
306
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
304
307
|
}
|
|
305
308
|
deleteNote(partIndex, staffIndex, measureIndex, noteIndex, voiceIndex = 0) {
|
|
306
309
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
@@ -309,11 +312,11 @@ export class Score {
|
|
|
309
312
|
const updatedMeasure = measure.deleteNote(noteIndex, voiceIndex);
|
|
310
313
|
return this.replaceMeasure(partIndex, staffIndex, measureIndex, updatedMeasure, true);
|
|
311
314
|
}
|
|
312
|
-
changeNoteDuration(partIndex, staffIndex, measureIndex, noteIndex, newDuration,
|
|
315
|
+
changeNoteDuration(partIndex, staffIndex, measureIndex, noteIndex, newDuration, dotCount = 0, voiceIndex = 0) {
|
|
313
316
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
314
317
|
return this;
|
|
315
318
|
const measure = this.parts[partIndex].staves[staffIndex].measures[measureIndex];
|
|
316
|
-
const updatedMeasure = measure.changeNoteDuration(noteIndex, newDuration,
|
|
319
|
+
const updatedMeasure = measure.changeNoteDuration(noteIndex, newDuration, dotCount, voiceIndex);
|
|
317
320
|
return this.replaceMeasure(partIndex, staffIndex, measureIndex, updatedMeasure, true);
|
|
318
321
|
}
|
|
319
322
|
moveNoteToVoice(partIndex, staffIndex, measureIndex, noteIndex, fromVoiceIndex, toVoiceIndex) {
|
|
@@ -328,78 +331,80 @@ export class Score {
|
|
|
328
331
|
pasteNotes(partIndex, staffIndex, measureIndex, noteIndex, notesToPaste) {
|
|
329
332
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
330
333
|
return this;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
334
|
+
const state = {
|
|
335
|
+
score: this,
|
|
336
|
+
mIdx: measureIndex,
|
|
337
|
+
nIdx: noteIndex,
|
|
338
|
+
queue: [...notesToPaste],
|
|
339
|
+
};
|
|
340
|
+
while (state.queue.length > 0) {
|
|
341
|
+
const noteToPaste = state.queue.shift();
|
|
342
|
+
const part = state.score.parts[partIndex];
|
|
338
343
|
if (!part)
|
|
339
344
|
break;
|
|
340
345
|
const staff = part.staves[staffIndex];
|
|
341
|
-
if (!staff ||
|
|
346
|
+
if (!staff || state.mIdx >= staff.measures.length)
|
|
342
347
|
break;
|
|
343
|
-
const measure = staff.measures[
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
queue.unshift(noteToPaste);
|
|
348
|
+
const measure = staff.measures[state.mIdx];
|
|
349
|
+
if (state.nIdx >= measure.notes.length) {
|
|
350
|
+
state.mIdx++;
|
|
351
|
+
state.nIdx = 0;
|
|
352
|
+
state.queue.unshift(noteToPaste);
|
|
348
353
|
continue;
|
|
349
354
|
}
|
|
350
355
|
let available = 0;
|
|
351
|
-
for (let i =
|
|
356
|
+
for (let i = state.nIdx; i < measure.notes.length; i++)
|
|
352
357
|
available += measure.notes[i].getDurationValue();
|
|
353
358
|
const needed = noteToPaste.getDurationValue();
|
|
354
359
|
if (needed <= available + 0.001) {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
360
|
+
state.score = state.score.changeNoteDuration(partIndex, staffIndex, state.mIdx, state.nIdx, noteToPaste.duration, noteToPaste.dotCount);
|
|
361
|
+
state.score = state.score.replaceNote(partIndex, staffIndex, state.mIdx, state.nIdx, noteToPaste);
|
|
362
|
+
state.nIdx++;
|
|
358
363
|
}
|
|
359
364
|
else {
|
|
360
365
|
const parts = decomposeDuration(available);
|
|
361
366
|
if (parts.length === 0) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
queue.unshift(noteToPaste);
|
|
367
|
+
state.mIdx++;
|
|
368
|
+
state.nIdx = 0;
|
|
369
|
+
state.queue.unshift(noteToPaste);
|
|
365
370
|
continue;
|
|
366
371
|
}
|
|
367
372
|
const fillPart = parts[0];
|
|
368
373
|
const filledNote = noteToPaste
|
|
369
|
-
.withDuration(fillPart.duration, fillPart.
|
|
374
|
+
.withDuration(fillPart.duration, fillPart.dotCount)
|
|
370
375
|
.withTie(true);
|
|
371
|
-
|
|
372
|
-
|
|
376
|
+
state.score = state.score.changeNoteDuration(partIndex, staffIndex, state.mIdx, state.nIdx, fillPart.duration, fillPart.dotCount);
|
|
377
|
+
state.score = state.score.replaceNote(partIndex, staffIndex, state.mIdx, state.nIdx, filledNote);
|
|
373
378
|
const remainderParts = decomposeDuration(needed - fillPart.val);
|
|
374
379
|
const remainderNotes = remainderParts.map((p, idx) => {
|
|
375
380
|
const isLast = idx === remainderParts.length - 1;
|
|
376
381
|
return noteToPaste
|
|
377
|
-
.withDuration(p.duration, p.
|
|
378
|
-
.withTie(isLast ? !!noteToPaste.tie : true);
|
|
382
|
+
.withDuration(p.duration, p.dotCount)
|
|
383
|
+
.withTie(isLast ? !!noteToPaste.notes[0].tie : true);
|
|
379
384
|
});
|
|
380
|
-
queue.unshift(...remainderNotes);
|
|
381
|
-
|
|
385
|
+
state.queue.unshift(...remainderNotes);
|
|
386
|
+
state.nIdx++;
|
|
382
387
|
}
|
|
383
388
|
}
|
|
384
|
-
return
|
|
389
|
+
return state.score;
|
|
385
390
|
}
|
|
386
|
-
withTempo(bpm, duration,
|
|
387
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, duration,
|
|
391
|
+
withTempo(bpm, duration, dotCount = 0) {
|
|
392
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, duration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, dotCount);
|
|
388
393
|
}
|
|
389
394
|
withTempoText(text) {
|
|
390
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
395
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, text, this.tempoDotCount);
|
|
391
396
|
}
|
|
392
397
|
withSwing(swing) {
|
|
393
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
398
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
394
399
|
}
|
|
395
400
|
withLyricist(lyricist) {
|
|
396
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.
|
|
401
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.copyright, lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
397
402
|
}
|
|
398
403
|
withCopyright(copyright) {
|
|
399
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration,
|
|
404
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
400
405
|
}
|
|
401
406
|
withParts(parts) {
|
|
402
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, parts, this.bpm, this.tempoDuration, this.
|
|
407
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, parts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
403
408
|
}
|
|
404
409
|
toJSON() {
|
|
405
410
|
return {
|
|
@@ -410,7 +415,6 @@ export class Score {
|
|
|
410
415
|
parts: this.parts.map((p) => p.toJSON()),
|
|
411
416
|
bpm: this.bpm,
|
|
412
417
|
tempoDuration: this.tempoDuration,
|
|
413
|
-
tempoIsDotted: this.tempoIsDotted,
|
|
414
418
|
copyright: this.copyright,
|
|
415
419
|
lyricist: this.lyricist,
|
|
416
420
|
swing: this.swing,
|
|
@@ -425,92 +429,111 @@ export class Score {
|
|
|
425
429
|
return this;
|
|
426
430
|
const newParts = [...this.parts];
|
|
427
431
|
newParts[partIndex] = newPart;
|
|
428
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.
|
|
432
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
429
433
|
}
|
|
430
434
|
addPart(newPart) {
|
|
431
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, [...this.parts, newPart], this.bpm, this.tempoDuration, this.
|
|
435
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, [...this.parts, newPart], this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
432
436
|
}
|
|
433
437
|
removePart(partIndex) {
|
|
434
438
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
435
439
|
return this;
|
|
436
440
|
const newParts = this.parts.filter((_, i) => i !== partIndex);
|
|
437
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.
|
|
441
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
438
442
|
}
|
|
439
443
|
replaceStaff(partIndex, staffIndex, newStaff) {
|
|
440
444
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
441
445
|
return this;
|
|
442
446
|
const newParts = [...this.parts];
|
|
443
447
|
newParts[partIndex] = newParts[partIndex].replaceStaff(staffIndex, newStaff);
|
|
444
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.
|
|
448
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
445
449
|
}
|
|
446
450
|
addMeasure(index, measure) {
|
|
447
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.addMeasure(index, measure)), this.bpm, this.tempoDuration, this.
|
|
451
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.addMeasure(index, measure)), this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
448
452
|
}
|
|
449
453
|
deleteMeasure(index) {
|
|
450
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.deleteMeasure(index)), this.bpm, this.tempoDuration, this.
|
|
454
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.deleteMeasure(index)), this.bpm, this.tempoDuration, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText, this.tempoDotCount);
|
|
455
|
+
}
|
|
456
|
+
deleteMeasures(startIndex, endIndex) {
|
|
457
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
458
|
+
let currentScore = this;
|
|
459
|
+
const start = Math.min(startIndex, endIndex);
|
|
460
|
+
const end = Math.max(startIndex, endIndex);
|
|
461
|
+
for (let i = end; i >= start; i--) {
|
|
462
|
+
currentScore = currentScore.deleteMeasure(i);
|
|
463
|
+
}
|
|
464
|
+
return currentScore;
|
|
451
465
|
}
|
|
452
466
|
getPlaybackSequence() {
|
|
453
467
|
const measures = this.parts[0]?.staves[0]?.measures || [];
|
|
454
468
|
const sequence = [];
|
|
455
469
|
let i = 0;
|
|
456
470
|
const startRepeatStack = [0]; // Implicit start at 0
|
|
457
|
-
|
|
458
|
-
|
|
471
|
+
const iterationMap = new Map(); // startMeasureIndex -> currentIteration
|
|
472
|
+
iterationMap.set(0, 1);
|
|
473
|
+
const repeatJumpCount = new Map(); // endMeasureIndex -> count
|
|
459
474
|
let safetyCounter = 0;
|
|
460
475
|
const MAX_SEQUENCE = 10000;
|
|
476
|
+
let activeVoltaNumbers = [];
|
|
477
|
+
let pendingPop = false;
|
|
461
478
|
while (i < measures.length && safetyCounter < MAX_SEQUENCE) {
|
|
462
479
|
safetyCounter++;
|
|
463
480
|
const m = measures[i];
|
|
464
|
-
//
|
|
481
|
+
// If we finished a repeat pass but the next measure still has a volta,
|
|
482
|
+
// we delay popping the stack so the volta logic can see the correct iteration count.
|
|
483
|
+
if (pendingPop && !m.volta) {
|
|
484
|
+
if (startRepeatStack.length > 1) {
|
|
485
|
+
startRepeatStack.pop();
|
|
486
|
+
}
|
|
487
|
+
pendingPop = false;
|
|
488
|
+
}
|
|
489
|
+
// 1. Handle Start Repeat
|
|
465
490
|
if (m.repeats.some((r) => r.type === 'start')) {
|
|
466
|
-
|
|
467
|
-
if (i !== justJumpedTo) {
|
|
491
|
+
if (startRepeatStack[startRepeatStack.length - 1] !== i) {
|
|
468
492
|
startRepeatStack.push(i);
|
|
493
|
+
iterationMap.set(i, 1);
|
|
469
494
|
}
|
|
470
495
|
}
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
iteration = (repeatCount.get(i) || 0) + 1;
|
|
479
|
-
else {
|
|
480
|
-
let nextEndIdx = -1;
|
|
481
|
-
for (let j = i; j < measures.length; j++) {
|
|
482
|
-
if (measures[j].repeats.some((r) => r.type === 'end')) {
|
|
483
|
-
nextEndIdx = j;
|
|
484
|
-
break;
|
|
485
|
-
}
|
|
496
|
+
// 2. Identify current repetition context
|
|
497
|
+
const currentStartIdx = startRepeatStack[startRepeatStack.length - 1];
|
|
498
|
+
const iteration = iterationMap.get(currentStartIdx) || 1;
|
|
499
|
+
// 3. Volta Logic
|
|
500
|
+
if (m.volta) {
|
|
501
|
+
if (m.volta.type === 'start' || m.volta.type === 'both') {
|
|
502
|
+
activeVoltaNumbers = m.volta.numbers;
|
|
486
503
|
}
|
|
487
|
-
if (nextEndIdx !== -1)
|
|
488
|
-
iteration = (repeatCount.get(nextEndIdx) || 0) + 1;
|
|
489
504
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
505
|
+
const effectiveNumbers = (m.volta && m.volta.numbers.length > 0)
|
|
506
|
+
? m.volta.numbers
|
|
507
|
+
: activeVoltaNumbers;
|
|
508
|
+
const hasVoltaContext = m.volta || activeVoltaNumbers.length > 0;
|
|
509
|
+
const shouldSkip = hasVoltaContext &&
|
|
510
|
+
effectiveNumbers.length > 0 &&
|
|
511
|
+
!effectiveNumbers.includes(iteration);
|
|
512
|
+
if (!shouldSkip) {
|
|
513
|
+
sequence.push(i);
|
|
514
|
+
}
|
|
515
|
+
// 4. Reset volta if stopped
|
|
516
|
+
if (m.volta && (m.volta.type === 'stop' || m.volta.type === 'both')) {
|
|
517
|
+
activeVoltaNumbers = [];
|
|
493
518
|
}
|
|
494
|
-
|
|
519
|
+
// 5. Handle End Repeat
|
|
520
|
+
const endRepeat = m.repeats.find((r) => r.type === 'end');
|
|
495
521
|
if (endRepeat) {
|
|
496
522
|
const maxTimes = endRepeat.times || 2;
|
|
497
|
-
const currentCount =
|
|
523
|
+
const currentCount = repeatJumpCount.get(i) || 0;
|
|
498
524
|
if (currentCount + 1 < maxTimes) {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
continue;
|
|
525
|
+
repeatJumpCount.set(i, currentCount + 1);
|
|
526
|
+
iterationMap.set(currentStartIdx, iteration + 1);
|
|
527
|
+
i = currentStartIdx;
|
|
528
|
+
activeVoltaNumbers = []; // Reset volta context when jumping back
|
|
529
|
+
pendingPop = false;
|
|
530
|
+
continue; // Jump back
|
|
505
531
|
}
|
|
506
532
|
else {
|
|
507
|
-
// Finished
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
startRepeatStack.pop();
|
|
512
|
-
}
|
|
513
|
-
// Do not pop 0, so unmatched repeats will default to 0
|
|
533
|
+
// Finished this repeat group
|
|
534
|
+
repeatJumpCount.set(i, 0);
|
|
535
|
+
pendingPop = true;
|
|
536
|
+
activeVoltaNumbers = [];
|
|
514
537
|
}
|
|
515
538
|
}
|
|
516
539
|
i++;
|
package/dist/models/Staff.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare class Staff {
|
|
|
17
17
|
replaceNote(measureIndex: number, noteIndex: number, newNote: Note, voiceIndex?: number): Staff;
|
|
18
18
|
replaceMeasure(measureIndex: number, newMeasure: Measure): Staff;
|
|
19
19
|
deleteNote(measureIndex: number, noteIndex: number, voiceIndex?: number): Staff;
|
|
20
|
-
changeNoteDuration(measureIndex: number, noteIndex: number, newDuration: Duration,
|
|
20
|
+
changeNoteDuration(measureIndex: number, noteIndex: number, newDuration: Duration, dotCount?: number, voiceIndex?: number): Staff;
|
|
21
21
|
toJSON(): StaffJSON;
|
|
22
22
|
withClef(clef: Clef): Staff;
|
|
23
23
|
withLineCount(lineCount: number): Staff;
|
package/dist/models/Staff.js
CHANGED
|
@@ -46,11 +46,11 @@ export class Staff {
|
|
|
46
46
|
newMeasures[measureIndex] = newMeasures[measureIndex].deleteNote(noteIndex, voiceIndex);
|
|
47
47
|
return new Staff(this.clef, newMeasures, this.lineCount, this.tuning);
|
|
48
48
|
}
|
|
49
|
-
changeNoteDuration(measureIndex, noteIndex, newDuration,
|
|
49
|
+
changeNoteDuration(measureIndex, noteIndex, newDuration, dotCount = 0, voiceIndex = 0) {
|
|
50
50
|
if (measureIndex < 0 || measureIndex >= this.measures.length)
|
|
51
51
|
return this;
|
|
52
52
|
const newMeasures = [...this.measures];
|
|
53
|
-
newMeasures[measureIndex] = newMeasures[measureIndex].changeNoteDuration(noteIndex, newDuration,
|
|
53
|
+
newMeasures[measureIndex] = newMeasures[measureIndex].changeNoteDuration(noteIndex, newDuration, dotCount, voiceIndex);
|
|
54
54
|
return new Staff(this.clef, newMeasures, this.lineCount, this.tuning);
|
|
55
55
|
}
|
|
56
56
|
toJSON() {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/Arpeggio.d.ts
CHANGED
package/dist/types/Arpeggio.js
CHANGED
|
@@ -8,4 +8,6 @@ export var Articulation;
|
|
|
8
8
|
Articulation["Staccatissimo"] = "staccatissimo";
|
|
9
9
|
Articulation["Caesura"] = "caesura";
|
|
10
10
|
Articulation["BreathMark"] = "breath-mark";
|
|
11
|
+
Articulation["Legato"] = "legato";
|
|
12
|
+
Articulation["Portato"] = "portato";
|
|
11
13
|
})(Articulation || (Articulation = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/Duration.d.ts
CHANGED
package/dist/types/Duration.js
CHANGED
|
@@ -42,26 +42,26 @@ export function decomposeDuration(value) {
|
|
|
42
42
|
const result = [];
|
|
43
43
|
let remaining = value;
|
|
44
44
|
const options = [
|
|
45
|
-
{ val: 4, dur: Duration.Whole, dot:
|
|
46
|
-
{ val: 3, dur: Duration.Half, dot:
|
|
47
|
-
{ val: 2, dur: Duration.Half, dot:
|
|
48
|
-
{ val: 1.5, dur: Duration.Quarter, dot:
|
|
49
|
-
{ val: 1, dur: Duration.Quarter, dot:
|
|
50
|
-
{ val: 0.75, dur: Duration.Eighth, dot:
|
|
51
|
-
{ val: 0.5, dur: Duration.Eighth, dot:
|
|
52
|
-
{ val: 0.375, dur: Duration.Sixteenth, dot:
|
|
53
|
-
{ val: 0.25, dur: Duration.Sixteenth, dot:
|
|
54
|
-
{ val: 0.1875, dur: Duration.ThirtySecond, dot:
|
|
55
|
-
{ val: 0.125, dur: Duration.ThirtySecond, dot:
|
|
56
|
-
{ val: 0.09375, dur: Duration.SixtyFourth, dot:
|
|
57
|
-
{ val: 0.0625, dur: Duration.SixtyFourth, dot:
|
|
45
|
+
{ val: 4, dur: Duration.Whole, dot: 0 },
|
|
46
|
+
{ val: 3, dur: Duration.Half, dot: 1 },
|
|
47
|
+
{ val: 2, dur: Duration.Half, dot: 0 },
|
|
48
|
+
{ val: 1.5, dur: Duration.Quarter, dot: 1 },
|
|
49
|
+
{ val: 1, dur: Duration.Quarter, dot: 0 },
|
|
50
|
+
{ val: 0.75, dur: Duration.Eighth, dot: 1 },
|
|
51
|
+
{ val: 0.5, dur: Duration.Eighth, dot: 0 },
|
|
52
|
+
{ val: 0.375, dur: Duration.Sixteenth, dot: 1 },
|
|
53
|
+
{ val: 0.25, dur: Duration.Sixteenth, dot: 0 },
|
|
54
|
+
{ val: 0.1875, dur: Duration.ThirtySecond, dot: 1 },
|
|
55
|
+
{ val: 0.125, dur: Duration.ThirtySecond, dot: 0 },
|
|
56
|
+
{ val: 0.09375, dur: Duration.SixtyFourth, dot: 1 },
|
|
57
|
+
{ val: 0.0625, dur: Duration.SixtyFourth, dot: 0 },
|
|
58
58
|
];
|
|
59
59
|
// Protect against infinite loop with max iterations
|
|
60
60
|
let loops = 0;
|
|
61
61
|
while (remaining > 0.001 && loops < 100) {
|
|
62
62
|
const match = options.find((o) => o.val <= remaining + 0.001);
|
|
63
63
|
if (match) {
|
|
64
|
-
result.push({ duration: match.dur,
|
|
64
|
+
result.push({ duration: match.dur, dotCount: match.dot, val: match.val });
|
|
65
65
|
remaining -= match.val;
|
|
66
66
|
}
|
|
67
67
|
else {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types/Hairpin.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { Placement } from './Placement.js';
|
|
1
2
|
export declare enum HairpinType {
|
|
2
3
|
Crescendo = "crescendo",
|
|
3
4
|
Decrescendo = "decrescendo"
|
|
4
5
|
}
|
|
5
|
-
export interface Hairpin {
|
|
6
|
+
export interface Hairpin extends Placement {
|
|
6
7
|
type: HairpinType;
|
|
7
8
|
placement: 'start' | 'stop';
|
|
8
9
|
}
|