@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.
@@ -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() { return this.notes[0].pitch; }
154
- get accidental() { return this.notes[0].accidental; }
155
- get tie() { return this.notes[0].tie; }
156
- get slur() { return this.notes[0].slur; }
157
- get articulation() { return this.notes[0].articulation; }
158
- get ornament() { return this.notes[0].ornament; }
159
- get dynamic() { return this.notes[0].dynamic; }
160
- get hairpin() { return this.notes[0].hairpin; }
161
- get glissando() { return this.notes[0].glissando; }
162
- get arpeggio() { return this.notes[0].arpeggio; }
163
- get ottava() { return this.notes[0].ottava; }
164
- get pedal() { return this.notes[0].pedal; }
165
- get notehead() { return this.notes[0].notehead; }
166
- get color() { return this.notes[0].color; }
167
- get fret() { return this.notes[0].fret; }
168
- get string() { return this.notes[0].string; }
169
- get bowing() { return this.notes[0].bowing; }
170
- get fingering() { return this.notes[0].fingering; }
171
- get lyrics() { return this.notes[0].lyrics; }
172
- get lyric() { return this.notes[0].lyric; }
173
- get chord() { return this.notes[0].chord; }
174
- get fretboardDiagram() { return this.notes[0].fretboardDiagram; }
175
- get staffText() { return this.notes[0].staffText; }
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
- return new NoteSet(data.notes.map((n) => Note.fromJSON(n)));
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
  }
@@ -76,7 +76,13 @@ export class Pitch {
76
76
  }
77
77
  static fromNoteName(note, octave) {
78
78
  const baseSteps = {
79
- C: 0, D: 1, E: 2, F: 3, G: 4, A: 5, B: 6,
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;
@@ -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
- 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);
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
  }
@@ -17,7 +17,8 @@ export class Score {
17
17
  swing;
18
18
  subtitle;
19
19
  genre;
20
- constructor(title, composer, timeSignature, keySignature, parts, bpm = 120, tempoDuration = Duration.Quarter, tempoIsDotted = false, copyright = '', lyricist = '', swing = false, subtitle = '', genre = Genre.Unknown) {
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, 1: 7, 2: 2, 3: -3, 4: 4, 5: -1, 6: 6, 7: 1, 8: -4, 9: 3, 10: -2, 11: 5
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.withDuration(fillPart.duration, fillPart.isDotted).withTie(true);
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.withDuration(p.duration, p.isDotted).withTie(isLast ? !!noteToPaste.tie : true);
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, composer: this.composer, timeSignature: this.timeSignature, keySignature: this.keySignature,
205
- parts: this.parts.map((p) => p.toJSON()), bpm: this.bpm, tempoDuration: this.tempoDuration,
206
- tempoIsDotted: this.tempoIsDotted, copyright: this.copyright, lyricist: this.lyricist,
207
- swing: this.swing, subtitle: this.subtitle, genre: this.genre,
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 || [];
@@ -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
  /**
@@ -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
+ }