@scorelabs/core 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/importers/MusicXMLParser.d.ts +2 -0
- package/dist/importers/MusicXMLParser.js +388 -78
- package/dist/models/Instrument.d.ts +1 -0
- package/dist/models/Instrument.js +13 -2
- package/dist/models/Measure.js +22 -5
- package/dist/models/Note.d.ts +31 -6
- package/dist/models/Note.js +104 -39
- package/dist/models/NoteSet.d.ts +14 -3
- package/dist/models/NoteSet.js +125 -26
- package/dist/models/Pitch.js +7 -1
- package/dist/models/Score.d.ts +5 -1
- package/dist/models/Score.js +58 -25
- package/dist/models/types.d.ts +16 -2
- package/dist/models/types.js +11 -0
- package/dist/utils/tier.d.ts +36 -0
- package/dist/utils/tier.js +112 -0
- package/package.json +34 -31
package/dist/models/NoteSet.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Note } from './Note';
|
|
2
|
+
import { Duration } from './types';
|
|
2
3
|
export class NoteSet {
|
|
3
4
|
notes;
|
|
4
5
|
constructor(notes) {
|
|
@@ -71,11 +72,11 @@ export class NoteSet {
|
|
|
71
72
|
return new NoteSet([first.withRest(true)]);
|
|
72
73
|
}
|
|
73
74
|
else {
|
|
74
|
-
return new NoteSet(this.notes.map(n => n.withRest(false)));
|
|
75
|
+
return new NoteSet(this.notes.map((n) => n.withRest(false)));
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
withGrace(isGrace) {
|
|
78
|
-
return new NoteSet(this.notes.map(n => n.withGrace(isGrace)));
|
|
79
|
+
return new NoteSet(this.notes.map((n) => n.withGrace(isGrace)));
|
|
79
80
|
}
|
|
80
81
|
toggleEnharmonic() {
|
|
81
82
|
const newNotes = [...this.notes];
|
|
@@ -141,6 +142,21 @@ export class NoteSet {
|
|
|
141
142
|
withFingering(fingering) {
|
|
142
143
|
return new NoteSet(this.notes.map((n) => n.withFingering(fingering)));
|
|
143
144
|
}
|
|
145
|
+
withHammerOn(type) {
|
|
146
|
+
return new NoteSet(this.notes.map((n) => n.withHammerOn(type)));
|
|
147
|
+
}
|
|
148
|
+
withPullOff(type) {
|
|
149
|
+
return new NoteSet(this.notes.map((n) => n.withPullOff(type)));
|
|
150
|
+
}
|
|
151
|
+
withPalmMute(type) {
|
|
152
|
+
return new NoteSet(this.notes.map((n) => n.withPalmMute(type)));
|
|
153
|
+
}
|
|
154
|
+
withStringCircled(isStringCircled) {
|
|
155
|
+
return new NoteSet(this.notes.map((n) => n.withStringCircled(isStringCircled)));
|
|
156
|
+
}
|
|
157
|
+
withStemDirection(dir) {
|
|
158
|
+
return new NoteSet(this.notes.map((n) => n.withStemDirection(dir)));
|
|
159
|
+
}
|
|
144
160
|
withLyric(lyric) {
|
|
145
161
|
return new NoteSet(this.notes.map((n) => n.withLyric(lyric)));
|
|
146
162
|
}
|
|
@@ -150,35 +166,118 @@ export class NoteSet {
|
|
|
150
166
|
withFretboardDiagram(diagram) {
|
|
151
167
|
return new NoteSet(this.notes.map((n) => n.withFretboardDiagram(diagram)));
|
|
152
168
|
}
|
|
153
|
-
get pitch() {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
get
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
get
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
get
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
get
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
get
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
get
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
get
|
|
175
|
-
|
|
169
|
+
get pitch() {
|
|
170
|
+
return this.notes[0].pitch;
|
|
171
|
+
}
|
|
172
|
+
get accidental() {
|
|
173
|
+
return this.notes[0].accidental;
|
|
174
|
+
}
|
|
175
|
+
get tie() {
|
|
176
|
+
return this.notes[0].tie;
|
|
177
|
+
}
|
|
178
|
+
get slur() {
|
|
179
|
+
return this.notes[0].slur;
|
|
180
|
+
}
|
|
181
|
+
get articulation() {
|
|
182
|
+
return this.notes[0].articulation;
|
|
183
|
+
}
|
|
184
|
+
get ornament() {
|
|
185
|
+
return this.notes[0].ornament;
|
|
186
|
+
}
|
|
187
|
+
get dynamic() {
|
|
188
|
+
return this.notes[0].dynamic;
|
|
189
|
+
}
|
|
190
|
+
get hairpin() {
|
|
191
|
+
return this.notes[0].hairpin;
|
|
192
|
+
}
|
|
193
|
+
get glissando() {
|
|
194
|
+
return this.notes[0].glissando;
|
|
195
|
+
}
|
|
196
|
+
get arpeggio() {
|
|
197
|
+
return this.notes[0].arpeggio;
|
|
198
|
+
}
|
|
199
|
+
get ottava() {
|
|
200
|
+
return this.notes[0].ottava;
|
|
201
|
+
}
|
|
202
|
+
get pedal() {
|
|
203
|
+
return this.notes[0].pedal;
|
|
204
|
+
}
|
|
205
|
+
get notehead() {
|
|
206
|
+
return this.notes[0].notehead;
|
|
207
|
+
}
|
|
208
|
+
get color() {
|
|
209
|
+
return this.notes[0].color;
|
|
210
|
+
}
|
|
211
|
+
get fret() {
|
|
212
|
+
return this.notes[0].fret;
|
|
213
|
+
}
|
|
214
|
+
get string() {
|
|
215
|
+
return this.notes[0].string;
|
|
216
|
+
}
|
|
217
|
+
get bowing() {
|
|
218
|
+
return this.notes[0].bowing;
|
|
219
|
+
}
|
|
220
|
+
get fingering() {
|
|
221
|
+
return this.notes[0].fingering;
|
|
222
|
+
}
|
|
223
|
+
get allLyrics() {
|
|
224
|
+
return this.notes[0].allLyrics;
|
|
225
|
+
}
|
|
226
|
+
get lyrics() {
|
|
227
|
+
return this.notes[0].lyrics;
|
|
228
|
+
}
|
|
229
|
+
get lyric() {
|
|
230
|
+
return this.notes[0].lyric;
|
|
231
|
+
}
|
|
232
|
+
get chord() {
|
|
233
|
+
return this.notes[0].chord;
|
|
234
|
+
}
|
|
235
|
+
get fretboardDiagram() {
|
|
236
|
+
return this.notes[0].fretboardDiagram;
|
|
237
|
+
}
|
|
238
|
+
get staffText() {
|
|
239
|
+
return this.notes[0].staffText;
|
|
240
|
+
}
|
|
241
|
+
get hammerOn() {
|
|
242
|
+
return this.notes[0].hammerOn;
|
|
243
|
+
}
|
|
244
|
+
get pullOff() {
|
|
245
|
+
return this.notes[0].pullOff;
|
|
246
|
+
}
|
|
247
|
+
get palmMute() {
|
|
248
|
+
return this.notes[0].palmMute;
|
|
249
|
+
}
|
|
250
|
+
get isStringCircled() {
|
|
251
|
+
return this.notes[0].isStringCircled;
|
|
252
|
+
}
|
|
253
|
+
get stemDirection() {
|
|
254
|
+
return this.notes[0].stemDirection;
|
|
255
|
+
}
|
|
176
256
|
toJSON() {
|
|
177
257
|
return {
|
|
178
258
|
notes: this.notes.map((n) => n.toJSON()),
|
|
179
259
|
};
|
|
180
260
|
}
|
|
181
261
|
static fromJSON(data) {
|
|
182
|
-
|
|
262
|
+
if (!data) {
|
|
263
|
+
console.warn('NoteSet.fromJSON: received null or undefined data');
|
|
264
|
+
return new NoteSet([new Note(Duration.Quarter, undefined, true)]);
|
|
265
|
+
}
|
|
266
|
+
let notesArray;
|
|
267
|
+
if (data.notes && Array.isArray(data.notes)) {
|
|
268
|
+
notesArray = data.notes;
|
|
269
|
+
}
|
|
270
|
+
else if (Array.isArray(data)) {
|
|
271
|
+
notesArray = data;
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// Treat it as a single NoteJSON
|
|
275
|
+
notesArray = [data];
|
|
276
|
+
}
|
|
277
|
+
return new NoteSet(notesArray.map((n) => {
|
|
278
|
+
if (n instanceof Note)
|
|
279
|
+
return n;
|
|
280
|
+
return Note.fromJSON(n);
|
|
281
|
+
}));
|
|
183
282
|
}
|
|
184
283
|
}
|
package/dist/models/Pitch.js
CHANGED
|
@@ -76,7 +76,13 @@ export class Pitch {
|
|
|
76
76
|
}
|
|
77
77
|
static fromNoteName(note, octave) {
|
|
78
78
|
const baseSteps = {
|
|
79
|
-
C: 0,
|
|
79
|
+
C: 0,
|
|
80
|
+
D: 1,
|
|
81
|
+
E: 2,
|
|
82
|
+
F: 3,
|
|
83
|
+
G: 4,
|
|
84
|
+
A: 5,
|
|
85
|
+
B: 6,
|
|
80
86
|
};
|
|
81
87
|
const stepLabel = note[0].toUpperCase();
|
|
82
88
|
const step = baseSteps[stepLabel] ?? 0;
|
package/dist/models/Score.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ export declare class Score {
|
|
|
20
20
|
readonly swing: boolean;
|
|
21
21
|
readonly subtitle: string;
|
|
22
22
|
readonly genre: Genre | string;
|
|
23
|
-
|
|
23
|
+
readonly tempoText: string;
|
|
24
|
+
constructor(title: string, composer: string, timeSignature: TimeSignature, keySignature: KeySignature, parts: Part[], bpm?: number, tempoDuration?: Duration, tempoIsDotted?: boolean, copyright?: string, lyricist?: string, swing?: boolean, subtitle?: string, genre?: Genre | string, tempoText?: string);
|
|
24
25
|
withTitle(title: string): Score;
|
|
25
26
|
withComposer(composer: string): Score;
|
|
26
27
|
withSubtitle(subtitle: string): Score;
|
|
@@ -42,9 +43,11 @@ export declare class Score {
|
|
|
42
43
|
changeNoteDuration(partIndex: number, staffIndex: number, measureIndex: number, noteIndex: number, newDuration: Duration, isDotted?: boolean, voiceIndex?: number): Score;
|
|
43
44
|
pasteNotes(partIndex: number, staffIndex: number, measureIndex: number, noteIndex: number, notesToPaste: NoteSet[]): Score;
|
|
44
45
|
withTempo(bpm: number, duration: Duration, isDotted: boolean): Score;
|
|
46
|
+
withTempoText(text: string): Score;
|
|
45
47
|
withSwing(swing: boolean): Score;
|
|
46
48
|
withLyricist(lyricist: string): Score;
|
|
47
49
|
withCopyright(copyright: string): Score;
|
|
50
|
+
withParts(parts: Part[]): Score;
|
|
48
51
|
toJSON(): ScoreJSON;
|
|
49
52
|
replacePart(partIndex: number, newPart: Part): Score;
|
|
50
53
|
addPart(newPart: Part): Score;
|
|
@@ -68,4 +71,5 @@ export interface ScoreJSON {
|
|
|
68
71
|
swing?: boolean;
|
|
69
72
|
subtitle?: string;
|
|
70
73
|
genre?: Genre | string;
|
|
74
|
+
tempoText?: string;
|
|
71
75
|
}
|
package/dist/models/Score.js
CHANGED
|
@@ -17,7 +17,8 @@ export class Score {
|
|
|
17
17
|
swing;
|
|
18
18
|
subtitle;
|
|
19
19
|
genre;
|
|
20
|
-
|
|
20
|
+
tempoText;
|
|
21
|
+
constructor(title, composer, timeSignature, keySignature, parts, bpm = 120, tempoDuration = Duration.Quarter, tempoIsDotted = false, copyright = '', lyricist = '', swing = false, subtitle = '', genre = Genre.Unknown, tempoText = '') {
|
|
21
22
|
this.title = title;
|
|
22
23
|
this.composer = composer;
|
|
23
24
|
this.timeSignature = timeSignature;
|
|
@@ -31,18 +32,19 @@ export class Score {
|
|
|
31
32
|
this.swing = swing;
|
|
32
33
|
this.subtitle = subtitle;
|
|
33
34
|
this.genre = genre;
|
|
35
|
+
this.tempoText = tempoText;
|
|
34
36
|
}
|
|
35
37
|
withTitle(title) {
|
|
36
|
-
return new Score(title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
38
|
+
return new Score(title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
37
39
|
}
|
|
38
40
|
withComposer(composer) {
|
|
39
|
-
return new Score(this.title, composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
41
|
+
return new Score(this.title, composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
40
42
|
}
|
|
41
43
|
withSubtitle(subtitle) {
|
|
42
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, subtitle, this.genre);
|
|
44
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, subtitle, this.genre, this.tempoText);
|
|
43
45
|
}
|
|
44
46
|
withGenre(genre) {
|
|
45
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, genre);
|
|
47
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, genre, this.tempoText);
|
|
46
48
|
}
|
|
47
49
|
getMeasureCount() {
|
|
48
50
|
return this.parts[0]?.getMeasureCount() ?? 0;
|
|
@@ -85,11 +87,22 @@ export class Score {
|
|
|
85
87
|
}
|
|
86
88
|
static fromJSON(data) {
|
|
87
89
|
const parts = data.parts.map((p) => Part.fromJSON(p));
|
|
88
|
-
return new Score(data.title, data.composer, data.timeSignature, data.keySignature, parts, data.bpm ?? 120, data.tempoDuration ?? Duration.Quarter, data.tempoIsDotted ?? false, data.copyright ?? '', data.lyricist ?? '', data.swing ?? false, data.subtitle ?? '', data.genre || Genre.Unknown);
|
|
90
|
+
return new Score(data.title, data.composer, data.timeSignature, data.keySignature, parts, data.bpm ?? 120, data.tempoDuration ?? Duration.Quarter, data.tempoIsDotted ?? false, data.copyright ?? '', data.lyricist ?? '', data.swing ?? false, data.subtitle ?? '', data.genre || Genre.Unknown, data.tempoText ?? '');
|
|
89
91
|
}
|
|
90
92
|
transpose(semitones) {
|
|
91
93
|
const semitoneToFifths = {
|
|
92
|
-
0: 0,
|
|
94
|
+
0: 0,
|
|
95
|
+
1: 7,
|
|
96
|
+
2: 2,
|
|
97
|
+
3: -3,
|
|
98
|
+
4: 4,
|
|
99
|
+
5: -1,
|
|
100
|
+
6: 6,
|
|
101
|
+
7: 1,
|
|
102
|
+
8: -4,
|
|
103
|
+
9: 3,
|
|
104
|
+
10: -2,
|
|
105
|
+
11: 5,
|
|
93
106
|
};
|
|
94
107
|
const normalizedSemitones = ((semitones % 12) + 12) % 12;
|
|
95
108
|
const fifthsChange = semitoneToFifths[normalizedSemitones];
|
|
@@ -98,7 +111,7 @@ export class Score {
|
|
|
98
111
|
newFifths -= 12;
|
|
99
112
|
while (newFifths < -7)
|
|
100
113
|
newFifths += 12;
|
|
101
|
-
return new Score(this.title, this.composer, this.timeSignature, { fifths: newFifths }, this.parts.map((p) => p.transpose(semitones)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
114
|
+
return new Score(this.title, this.composer, this.timeSignature, { fifths: newFifths }, this.parts.map((p) => p.transpose(semitones)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
102
115
|
}
|
|
103
116
|
replaceNote(partIndex, staffIndex, measureIndex, noteIndex, newNote, voiceIndex = 0) {
|
|
104
117
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
@@ -117,7 +130,7 @@ export class Score {
|
|
|
117
130
|
}
|
|
118
131
|
const newParts = [...this.parts];
|
|
119
132
|
newParts[partIndex] = newParts[partIndex].replaceMeasure(staffIndex, measureIndex, measureToUse);
|
|
120
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
133
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
121
134
|
}
|
|
122
135
|
deleteNote(partIndex, staffIndex, measureIndex, noteIndex, voiceIndex = 0) {
|
|
123
136
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
@@ -173,13 +186,17 @@ export class Score {
|
|
|
173
186
|
continue;
|
|
174
187
|
}
|
|
175
188
|
const fillPart = parts[0];
|
|
176
|
-
const filledNote = noteToPaste
|
|
189
|
+
const filledNote = noteToPaste
|
|
190
|
+
.withDuration(fillPart.duration, fillPart.isDotted)
|
|
191
|
+
.withTie(true);
|
|
177
192
|
newScore = newScore.changeNoteDuration(partIndex, staffIndex, currentMeasureIdx, currentNoteIdx, fillPart.duration, fillPart.isDotted);
|
|
178
193
|
newScore = newScore.replaceNote(partIndex, staffIndex, currentMeasureIdx, currentNoteIdx, filledNote);
|
|
179
194
|
const remainderParts = decomposeDuration(needed - fillPart.val);
|
|
180
195
|
const remainderNotes = remainderParts.map((p, idx) => {
|
|
181
196
|
const isLast = idx === remainderParts.length - 1;
|
|
182
|
-
return noteToPaste
|
|
197
|
+
return noteToPaste
|
|
198
|
+
.withDuration(p.duration, p.isDotted)
|
|
199
|
+
.withTie(isLast ? !!noteToPaste.tie : true);
|
|
183
200
|
});
|
|
184
201
|
queue.unshift(...remainderNotes);
|
|
185
202
|
currentNoteIdx++;
|
|
@@ -188,23 +205,39 @@ export class Score {
|
|
|
188
205
|
return newScore;
|
|
189
206
|
}
|
|
190
207
|
withTempo(bpm, duration, isDotted) {
|
|
191
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, duration, isDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
208
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, bpm, duration, isDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
209
|
+
}
|
|
210
|
+
withTempoText(text) {
|
|
211
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, text);
|
|
192
212
|
}
|
|
193
213
|
withSwing(swing) {
|
|
194
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, swing, this.subtitle, this.genre);
|
|
214
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, swing, this.subtitle, this.genre, this.tempoText);
|
|
195
215
|
}
|
|
196
216
|
withLyricist(lyricist) {
|
|
197
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, lyricist, this.swing, this.subtitle, this.genre);
|
|
217
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
198
218
|
}
|
|
199
219
|
withCopyright(copyright) {
|
|
200
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
220
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts, this.bpm, this.tempoDuration, this.tempoIsDotted, copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
221
|
+
}
|
|
222
|
+
withParts(parts) {
|
|
223
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, parts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
201
224
|
}
|
|
202
225
|
toJSON() {
|
|
203
226
|
return {
|
|
204
|
-
title: this.title,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
227
|
+
title: this.title,
|
|
228
|
+
composer: this.composer,
|
|
229
|
+
timeSignature: this.timeSignature,
|
|
230
|
+
keySignature: this.keySignature,
|
|
231
|
+
parts: this.parts.map((p) => p.toJSON()),
|
|
232
|
+
bpm: this.bpm,
|
|
233
|
+
tempoDuration: this.tempoDuration,
|
|
234
|
+
tempoIsDotted: this.tempoIsDotted,
|
|
235
|
+
copyright: this.copyright,
|
|
236
|
+
lyricist: this.lyricist,
|
|
237
|
+
swing: this.swing,
|
|
238
|
+
subtitle: this.subtitle,
|
|
239
|
+
genre: this.genre,
|
|
240
|
+
tempoText: this.tempoText,
|
|
208
241
|
};
|
|
209
242
|
}
|
|
210
243
|
replacePart(partIndex, newPart) {
|
|
@@ -212,29 +245,29 @@ export class Score {
|
|
|
212
245
|
return this;
|
|
213
246
|
const newParts = [...this.parts];
|
|
214
247
|
newParts[partIndex] = newPart;
|
|
215
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
248
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
216
249
|
}
|
|
217
250
|
addPart(newPart) {
|
|
218
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, [...this.parts, newPart], this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
251
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, [...this.parts, newPart], this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
219
252
|
}
|
|
220
253
|
removePart(partIndex) {
|
|
221
254
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
222
255
|
return this;
|
|
223
256
|
const newParts = this.parts.filter((_, i) => i !== partIndex);
|
|
224
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
257
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
225
258
|
}
|
|
226
259
|
replaceStaff(partIndex, staffIndex, newStaff) {
|
|
227
260
|
if (partIndex < 0 || partIndex >= this.parts.length)
|
|
228
261
|
return this;
|
|
229
262
|
const newParts = [...this.parts];
|
|
230
263
|
newParts[partIndex] = newParts[partIndex].replaceStaff(staffIndex, newStaff);
|
|
231
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
264
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, newParts, this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
232
265
|
}
|
|
233
266
|
addMeasure(index, measure) {
|
|
234
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.addMeasure(index, measure)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
267
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.addMeasure(index, measure)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
235
268
|
}
|
|
236
269
|
deleteMeasure(index) {
|
|
237
|
-
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.deleteMeasure(index)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre);
|
|
270
|
+
return new Score(this.title, this.composer, this.timeSignature, this.keySignature, this.parts.map((p) => p.deleteMeasure(index)), this.bpm, this.tempoDuration, this.tempoIsDotted, this.copyright, this.lyricist, this.swing, this.subtitle, this.genre, this.tempoText);
|
|
238
271
|
}
|
|
239
272
|
getPlaybackSequence() {
|
|
240
273
|
const measures = this.parts[0]?.staves[0]?.measures || [];
|
package/dist/models/types.d.ts
CHANGED
|
@@ -37,12 +37,25 @@ export declare enum Articulation {
|
|
|
37
37
|
Tenuto = "tenuto",
|
|
38
38
|
Marcato = "marcato",
|
|
39
39
|
Fermata = "fermata",
|
|
40
|
-
Staccatissimo = "staccatissimo"
|
|
40
|
+
Staccatissimo = "staccatissimo",
|
|
41
|
+
Caesura = "caesura",
|
|
42
|
+
BreathMark = "breath-mark"
|
|
41
43
|
}
|
|
42
44
|
export declare enum Bowing {
|
|
43
45
|
DownBow = "down-bow",
|
|
44
46
|
UpBow = "up-bow"
|
|
45
47
|
}
|
|
48
|
+
export declare enum Syllabic {
|
|
49
|
+
Single = "single",
|
|
50
|
+
Begin = "begin",
|
|
51
|
+
Middle = "middle",
|
|
52
|
+
End = "end"
|
|
53
|
+
}
|
|
54
|
+
export interface Lyric {
|
|
55
|
+
text: string;
|
|
56
|
+
syllabic?: Syllabic;
|
|
57
|
+
isExtension?: boolean;
|
|
58
|
+
}
|
|
46
59
|
export declare enum NoteheadShape {
|
|
47
60
|
Normal = "normal",
|
|
48
61
|
Cross = "cross",// X shape (percussion, spoken)
|
|
@@ -170,7 +183,8 @@ export declare enum Ornament {
|
|
|
170
183
|
Mordent = "mordent",
|
|
171
184
|
InvertedMordent = "inverted-mordent",
|
|
172
185
|
Turn = "turn",
|
|
173
|
-
InvertedTurn = "inverted-turn"
|
|
186
|
+
InvertedTurn = "inverted-turn",
|
|
187
|
+
Tremolo = "tremolo"
|
|
174
188
|
}
|
|
175
189
|
export declare const DURATION_VALUES: Record<Duration, number>;
|
|
176
190
|
/**
|
package/dist/models/types.js
CHANGED
|
@@ -48,6 +48,8 @@ export var Articulation;
|
|
|
48
48
|
Articulation["Marcato"] = "marcato";
|
|
49
49
|
Articulation["Fermata"] = "fermata";
|
|
50
50
|
Articulation["Staccatissimo"] = "staccatissimo";
|
|
51
|
+
Articulation["Caesura"] = "caesura";
|
|
52
|
+
Articulation["BreathMark"] = "breath-mark";
|
|
51
53
|
})(Articulation || (Articulation = {}));
|
|
52
54
|
// Bowing markings
|
|
53
55
|
export var Bowing;
|
|
@@ -55,6 +57,14 @@ export var Bowing;
|
|
|
55
57
|
Bowing["DownBow"] = "down-bow";
|
|
56
58
|
Bowing["UpBow"] = "up-bow";
|
|
57
59
|
})(Bowing || (Bowing = {}));
|
|
60
|
+
// Lyrics Syllabic types
|
|
61
|
+
export var Syllabic;
|
|
62
|
+
(function (Syllabic) {
|
|
63
|
+
Syllabic["Single"] = "single";
|
|
64
|
+
Syllabic["Begin"] = "begin";
|
|
65
|
+
Syllabic["Middle"] = "middle";
|
|
66
|
+
Syllabic["End"] = "end";
|
|
67
|
+
})(Syllabic || (Syllabic = {}));
|
|
58
68
|
// Notehead shapes
|
|
59
69
|
export var NoteheadShape;
|
|
60
70
|
(function (NoteheadShape) {
|
|
@@ -154,6 +164,7 @@ export var Ornament;
|
|
|
154
164
|
Ornament["InvertedMordent"] = "inverted-mordent";
|
|
155
165
|
Ornament["Turn"] = "turn";
|
|
156
166
|
Ornament["InvertedTurn"] = "inverted-turn";
|
|
167
|
+
Ornament["Tremolo"] = "tremolo";
|
|
157
168
|
})(Ornament || (Ornament = {}));
|
|
158
169
|
// Duration values in terms of quarter note = 1
|
|
159
170
|
export const DURATION_VALUES = {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export declare const TOKEN_MODULUS = 17;
|
|
2
|
+
export declare const EXPECTED_REMAINDER = 2;
|
|
3
|
+
export declare const EXPIRATION_MS: number;
|
|
4
|
+
export type UserTier = 'free' | 'premium';
|
|
5
|
+
/**
|
|
6
|
+
* Obfuscates the user's tier into a numeric token for the viewer.
|
|
7
|
+
* The viewer expects token % 17 === 2 for premium access.
|
|
8
|
+
*/
|
|
9
|
+
export declare function generateObfuscatedToken(tier: UserTier): number;
|
|
10
|
+
/**
|
|
11
|
+
* Packs token and timestamp into an obfuscated string.
|
|
12
|
+
*/
|
|
13
|
+
export declare function packToken(token: number, timestamp: number, salt: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Unpacks the token string and validates the signature.
|
|
16
|
+
*/
|
|
17
|
+
export declare function unpackToken(packed: string, salt: string): {
|
|
18
|
+
token: number;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
} | null;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the effective numeric token, checking for expiration.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getEffectiveToken(user: any): number;
|
|
25
|
+
/**
|
|
26
|
+
* Returns the effective tier from the obfuscated token
|
|
27
|
+
*/
|
|
28
|
+
export declare function getTierFromUser(user: any): UserTier;
|
|
29
|
+
/**
|
|
30
|
+
* Returns true only if the user has a valid, non-expired premium token.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isUserPremium(user: any): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Returns true only if the user has a valid, non-expired free token.
|
|
35
|
+
*/
|
|
36
|
+
export declare function isUserFree(user: any): boolean;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export const TOKEN_MODULUS = 17;
|
|
2
|
+
export const EXPECTED_REMAINDER = 2;
|
|
3
|
+
export const EXPIRATION_MS = 24 * 60 * 60 * 1000;
|
|
4
|
+
/**
|
|
5
|
+
* Obfuscates the user's tier into a numeric token for the viewer.
|
|
6
|
+
* The viewer expects token % 17 === 2 for premium access.
|
|
7
|
+
*/
|
|
8
|
+
export function generateObfuscatedToken(tier) {
|
|
9
|
+
const isPremium = tier === 'premium';
|
|
10
|
+
let base = Math.floor(Math.random() * 8999) + 1000;
|
|
11
|
+
if (isPremium) {
|
|
12
|
+
// Ensure token % 17 === 2
|
|
13
|
+
return base - (base % TOKEN_MODULUS) + EXPECTED_REMAINDER;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
// Ensure token % 17 !== 2
|
|
17
|
+
const result = base;
|
|
18
|
+
return result % TOKEN_MODULUS === EXPECTED_REMAINDER ? result + 1 : result;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Packs token and timestamp into an obfuscated string.
|
|
23
|
+
*/
|
|
24
|
+
export function packToken(token, timestamp, salt) {
|
|
25
|
+
// Use a more robust numeric salt
|
|
26
|
+
const saltNum = salt
|
|
27
|
+
.split('')
|
|
28
|
+
.reduce((acc, char, i) => acc + char.charCodeAt(0) * (i + 1), 0);
|
|
29
|
+
// Pack components
|
|
30
|
+
const tHex = token.toString(16);
|
|
31
|
+
// Obfuscate time using XOR with salt and token
|
|
32
|
+
const timeKey = (saltNum ^ token) % 1000000;
|
|
33
|
+
const oTime = (BigInt(timestamp) ^ BigInt(timeKey)).toString(16);
|
|
34
|
+
// Create a signature that binds token, time and salt
|
|
35
|
+
const sigPayload = `${token}:${timestamp}:${salt}`;
|
|
36
|
+
let sig = 0;
|
|
37
|
+
for (let i = 0; i < sigPayload.length; i++) {
|
|
38
|
+
sig = (sig << 5) - sig + sigPayload.charCodeAt(i);
|
|
39
|
+
sig |= 0; // Convert to 32bit integer
|
|
40
|
+
}
|
|
41
|
+
const sHex = Math.abs(sig).toString(16);
|
|
42
|
+
return `${tHex}-${oTime}-${sHex}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Unpacks the token string and validates the signature.
|
|
46
|
+
*/
|
|
47
|
+
export function unpackToken(packed, salt) {
|
|
48
|
+
try {
|
|
49
|
+
const [tHex, oTime, sHex] = packed.split('-');
|
|
50
|
+
if (!tHex || !oTime || !sHex)
|
|
51
|
+
return null;
|
|
52
|
+
const token = parseInt(tHex, 16);
|
|
53
|
+
const saltNum = salt
|
|
54
|
+
.split('')
|
|
55
|
+
.reduce((acc, char, i) => acc + char.charCodeAt(0) * (i + 1), 0);
|
|
56
|
+
const timeKey = (saltNum ^ token) % 1000000;
|
|
57
|
+
const timestamp = Number(BigInt(parseInt(oTime, 16)) ^ BigInt(timeKey));
|
|
58
|
+
// Validate signature
|
|
59
|
+
const sigPayload = `${token}:${timestamp}:${salt}`;
|
|
60
|
+
let sig = 0;
|
|
61
|
+
for (let i = 0; i < sigPayload.length; i++) {
|
|
62
|
+
sig = (sig << 5) - sig + sigPayload.charCodeAt(i);
|
|
63
|
+
sig |= 0;
|
|
64
|
+
}
|
|
65
|
+
const expectedSig = Math.abs(sig).toString(16);
|
|
66
|
+
if (sHex !== expectedSig)
|
|
67
|
+
return null;
|
|
68
|
+
return { token, timestamp };
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Returns the effective numeric token, checking for expiration.
|
|
76
|
+
*/
|
|
77
|
+
export function getEffectiveToken(user) {
|
|
78
|
+
if (!user)
|
|
79
|
+
return 0;
|
|
80
|
+
const accessToken = user.accessToken || user.access_token;
|
|
81
|
+
if (!accessToken) {
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
const salt = (user.uuid || user.id || 'default-salt').toString();
|
|
85
|
+
const unpacked = unpackToken(accessToken, salt);
|
|
86
|
+
if (!unpacked)
|
|
87
|
+
return 0;
|
|
88
|
+
const now = Date.now();
|
|
89
|
+
if (now - unpacked.timestamp >= EXPIRATION_MS || now < unpacked.timestamp) {
|
|
90
|
+
return 0; // Expired
|
|
91
|
+
}
|
|
92
|
+
return unpacked.token;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Returns the effective tier from the obfuscated token
|
|
96
|
+
*/
|
|
97
|
+
export function getTierFromUser(user) {
|
|
98
|
+
const token = getEffectiveToken(user);
|
|
99
|
+
return token % TOKEN_MODULUS === EXPECTED_REMAINDER ? 'premium' : 'free';
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Returns true only if the user has a valid, non-expired premium token.
|
|
103
|
+
*/
|
|
104
|
+
export function isUserPremium(user) {
|
|
105
|
+
return getTierFromUser(user) === 'premium';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Returns true only if the user has a valid, non-expired free token.
|
|
109
|
+
*/
|
|
110
|
+
export function isUserFree(user) {
|
|
111
|
+
return getTierFromUser(user) === 'free';
|
|
112
|
+
}
|