@tspro/web-music-score 3.2.0 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +38 -10
  2. package/README.md +189 -331
  3. package/dist/audio/index.d.mts +40 -1
  4. package/dist/audio/index.d.ts +40 -1
  5. package/dist/audio/index.js +1 -1
  6. package/dist/audio/index.mjs +2 -2
  7. package/dist/audio-cg/index.d.mts +3 -0
  8. package/dist/audio-cg/index.d.ts +3 -0
  9. package/dist/audio-cg/index.js +1 -1
  10. package/dist/audio-cg/index.mjs +2 -2
  11. package/dist/{chunk-LCTM7BID.mjs → chunk-YFPLOHP2.mjs} +2 -2
  12. package/dist/core/index.d.mts +12 -0
  13. package/dist/core/index.d.ts +12 -0
  14. package/dist/core/index.js +3 -2
  15. package/dist/core/index.mjs +4 -3
  16. package/dist/guitar-CaZJDA05.d.ts +35 -0
  17. package/dist/guitar-DdexKdN6.d.mts +35 -0
  18. package/dist/iife/index.global.js +11 -11
  19. package/dist/{interface-Bn5HFt_U.d.mts → music-objects-DJQ4d2OA.d.mts} +640 -136
  20. package/dist/{interface-BlNl69uT.d.ts → music-objects-Dc3kR-XF.d.ts} +640 -136
  21. package/dist/note-eA2xPPiG.d.mts +294 -0
  22. package/dist/note-eA2xPPiG.d.ts +294 -0
  23. package/dist/pieces/index.d.mts +22 -3
  24. package/dist/pieces/index.d.ts +22 -3
  25. package/dist/pieces/index.js +7 -7
  26. package/dist/pieces/index.mjs +11 -11
  27. package/dist/react-ui/index.d.mts +166 -17
  28. package/dist/react-ui/index.d.ts +166 -17
  29. package/dist/react-ui/index.js +78 -1
  30. package/dist/react-ui/index.mjs +79 -2
  31. package/dist/scale-B2Icbetz.d.ts +230 -0
  32. package/dist/scale-BbDJTbrG.d.mts +230 -0
  33. package/dist/score/index.d.mts +359 -39
  34. package/dist/score/index.d.ts +359 -39
  35. package/dist/score/index.js +1252 -594
  36. package/dist/score/index.mjs +1255 -599
  37. package/dist/tempo-CtUhvJbr.d.mts +369 -0
  38. package/dist/tempo-Dt8aHpol.d.ts +369 -0
  39. package/dist/theory/index.d.mts +29 -13
  40. package/dist/theory/index.d.ts +29 -13
  41. package/dist/theory/index.js +583 -96
  42. package/dist/theory/index.mjs +580 -94
  43. package/package.json +2 -2
  44. package/dist/guitar-C2Cp71NZ.d.ts +0 -17
  45. package/dist/guitar-DggbM2UL.d.mts +0 -17
  46. package/dist/note-BFa43I86.d.mts +0 -85
  47. package/dist/note-BFa43I86.d.ts +0 -85
  48. package/dist/scale-DRR-t4Kr.d.mts +0 -74
  49. package/dist/scale-ebJm37q1.d.ts +0 -74
  50. package/dist/tempo-B4h5Ktob.d.mts +0 -104
  51. package/dist/tempo-DgqDEsn0.d.ts +0 -104
@@ -1,7 +1,7 @@
1
- /* WebMusicScore v3.2.0 | (c) 2023 PahkaSoft | MIT License | Includes: Tone.js (MIT License) */
1
+ /* WebMusicScore v4.0.1 | (c) 2023 PahkaSoft | MIT License | Includes: Tone.js (MIT License) */
2
2
  import {
3
3
  __publicField
4
- } from "../chunk-LCTM7BID.mjs";
4
+ } from "../chunk-YFPLOHP2.mjs";
5
5
 
6
6
  // src/theory/chord.ts
7
7
  import { Utils as Utils6 } from "@tspro/ts-utils-lib";
@@ -25,10 +25,10 @@ var PitchNotation = /* @__PURE__ */ ((PitchNotation2) => {
25
25
  var PitchNotationList = Utils.Enum.getEnumValues(PitchNotation);
26
26
  var DefaultPitchNotation = 0 /* Scientific */;
27
27
  function validatePitchNotation(pn) {
28
- if (!Utils.Is.isEnumValue(pn, PitchNotation)) {
29
- throw new MusicError(MusicErrorType.InvalidArg, `Invalid pitchNotation: ${pn}`);
30
- } else {
28
+ if (Utils.Is.isEnumValue(pn, PitchNotation)) {
31
29
  return pn;
30
+ } else {
31
+ throw new MusicError(MusicErrorType.InvalidArg, `Invalid pitchNotation: ${pn}`);
32
32
  }
33
33
  }
34
34
  function getPitchNotationName(pn) {
@@ -43,10 +43,10 @@ var GuitarNoteLabel = /* @__PURE__ */ ((GuitarNoteLabel2) => {
43
43
  var DefaultGuitarNoteLabel = "Default" /* Default */;
44
44
  var GuitarNoteLabelList = Utils.Enum.getEnumValues(GuitarNoteLabel);
45
45
  function validateGuitarNoteLabel(label) {
46
- if (!Utils.Is.isEnumValue(label, GuitarNoteLabel)) {
47
- throw new MusicError(MusicErrorType.Timesignature, `Invalid guitarNoteLabel: ${label}`);
48
- } else {
46
+ if (Utils.Is.isEnumValue(label, GuitarNoteLabel)) {
49
47
  return label;
48
+ } else {
49
+ throw new MusicError(MusicErrorType.Timesignature, `Invalid guitarNoteLabel: ${label}`);
50
50
  }
51
51
  }
52
52
 
@@ -65,8 +65,11 @@ var DiatonicToChromaticMap = [0, 2, 4, 5, 7, 9, 11];
65
65
  var NoteNameRegex = /^([A-G])((?:bb|𝄫|♭|b|#|♯|x|𝄪)?)?(-?\d+)?$/;
66
66
  var _Note = class _Note {
67
67
  constructor(arg, accidental, octave) {
68
+ /** Diatonic class */
68
69
  __publicField(this, "diatonicClass");
70
+ /** Accidental. */
69
71
  __publicField(this, "accidental");
72
+ /** Octave. */
70
73
  __publicField(this, "octave");
71
74
  if (typeof arg === "number" && typeof accidental === "number" && octave === void 0) {
72
75
  _Note.validateDiatonicId(arg);
@@ -85,21 +88,32 @@ var _Note = class _Note {
85
88
  throw new MusicError2(MusicErrorType2.Note, `Invalid args: ${arg}, ${accidental}, ${octave}`);
86
89
  }
87
90
  }
91
+ /** Diatonic id getter. */
88
92
  get diatonicId() {
89
93
  return _Note.getDiatonicIdInOctave(this.diatonicClass, this.octave);
90
94
  }
95
+ /** Chromatic id getter. */
91
96
  get chromaticId() {
92
97
  return _Note.getChromaticIdInOctave(DiatonicToChromaticMap[this.diatonicClass] + this.accidental, this.octave);
93
98
  }
99
+ /** Midi number getter (implemented same as chromatic id). */
94
100
  get midiNumber() {
95
101
  return this.chromaticId;
96
102
  }
103
+ /** Chromatic class getter. */
97
104
  get chromaticClass() {
98
105
  return _Note.getChromaticClass(DiatonicToChromaticMap[this.diatonicClass] + this.accidental);
99
106
  }
107
+ /** Note letter getter. */
100
108
  get noteLetter() {
101
109
  return NoteLetters[this.diatonicClass];
102
110
  }
111
+ /**
112
+ * Format note to string presentation.
113
+ * @param pitchNotation - Pitchy notation.
114
+ * @param symbolSet - Symbol set.
115
+ * @returns - String presentation of note.
116
+ */
103
117
  format(pitchNotation, symbolSet) {
104
118
  let { noteLetter, octave } = this;
105
119
  let accidentalSymbol = _Note.getAccidentalSymbol(this.accidental, symbolSet);
@@ -113,11 +127,21 @@ var _Note = class _Note {
113
127
  return noteLetter + accidentalSymbol + octave;
114
128
  }
115
129
  }
130
+ /**
131
+ * Format note to string presentation without octave number.
132
+ * @param symbolSet - Symbol set.
133
+ * @returns - String presentation of note without octave number.
134
+ */
116
135
  formatOmitOctave(symbolSet) {
117
136
  let noteLetter = NoteLetters[this.diatonicClass];
118
137
  let accidental = _Note.getAccidentalSymbol(this.accidental, symbolSet);
119
138
  return noteLetter + accidental;
120
139
  }
140
+ /**
141
+ * Get note.
142
+ * @param noteName - Note name (e.g. "C4").
143
+ * @returns - Note.
144
+ */
121
145
  static getNote(noteName) {
122
146
  let note = this.noteByNameCache.get(noteName);
123
147
  if (note === void 0) {
@@ -133,6 +157,11 @@ var _Note = class _Note {
133
157
  }
134
158
  return note;
135
159
  }
160
+ /**
161
+ * Get chromatic note. There are number of alternatives, this function uses simple logic to choose one.
162
+ * @param chromaticId - Chromatic id.
163
+ * @returns - Note.
164
+ */
136
165
  static getChromaticNote(chromaticId) {
137
166
  let note = this.chromaticNoteCache.get(chromaticId);
138
167
  if (note === void 0) {
@@ -159,21 +188,54 @@ var _Note = class _Note {
159
188
  throw new MusicError2(MusicErrorType2.Note, `Invalid arg: ${arg}`);
160
189
  }
161
190
  }
191
+ /**
192
+ * Get octave from diatonic id.
193
+ * @param diatonicId - Diatonic id.
194
+ * @returns - Octave.
195
+ */
162
196
  static getOctaveFromDiatonicId(diatonicId) {
163
197
  return Math.floor((diatonicId - C0_diatonicId) / 7);
164
198
  }
199
+ /**
200
+ * Get diatonic id in given octave (transposes diatonic id to given octave).
201
+ * @param diatonicId - Original diatonic id.
202
+ * @param octave - Octave.
203
+ * @returns - Transposed diatonic id.
204
+ */
165
205
  static getDiatonicIdInOctave(diatonicId, octave) {
166
206
  return _Note.getDiatonicClass(diatonicId) + octave * 7 + C0_diatonicId;
167
207
  }
208
+ /**
209
+ * Get chromatic class from chromatic id.
210
+ * @param chromaticId - Chromatic id.
211
+ * @returns - Chromatic class.
212
+ */
168
213
  static getChromaticClass(chromaticId) {
169
214
  return mod(chromaticId, 12);
170
215
  }
216
+ /**
217
+ * Get octave from chromatic id.
218
+ * @param chromaticId - Chromatic id.
219
+ * @returns - Octave.
220
+ */
171
221
  static getOctaveFromChromaticId(chromaticId) {
172
222
  return Math.floor((chromaticId - C0_chromaticId) / 12);
173
223
  }
224
+ /**
225
+ * Get chromatic id in given octave (transposes chromatic id to given octave).
226
+ * @param chromaticId - Original chromatic id.
227
+ * @param octave - Octave.
228
+ * @returns - Transpose chromatic id.
229
+ */
174
230
  static getChromaticIdInOctave(chromaticId, octave) {
175
231
  return _Note.getChromaticClass(chromaticId) + octave * 12 + C0_chromaticId;
176
232
  }
233
+ /**
234
+ * Test if given two notes are equal.
235
+ * @param a - Note a.
236
+ * @param b - Note b.
237
+ * @returns - True/false.
238
+ */
177
239
  static equals(a, b) {
178
240
  if (a == null && b == null) {
179
241
  return true;
@@ -183,6 +245,12 @@ var _Note = class _Note {
183
245
  return a === b || a.diatonicId === b.diatonicId && a.accidental === b.accidental;
184
246
  }
185
247
  }
248
+ /**
249
+ * Replace accidental symbols in given string to givn symbol set (ascii/unicode).
250
+ * @param str - String to replace.
251
+ * @param symbolSet - Symbol set.
252
+ * @returns - String with updated accidental symbols.
253
+ */
186
254
  static replaceAccidentalSymbols(str, symbolSet) {
187
255
  if (symbolSet === 1 /* Unicode */) {
188
256
  return str.replace("bb", "\u{1D12B}").replace("b", "\u266D").replace("#", "\u266F").replace("x", "\u{1D12A}");
@@ -190,9 +258,19 @@ var _Note = class _Note {
190
258
  return str.replace("\u{1D12B}", "bb").replace("\u266D", "b").replace("\u266F", "#").replace("\u{1D12A}", "x");
191
259
  }
192
260
  }
261
+ /**
262
+ * Test if given string is valid note name.
263
+ * @param noteName - Note name to validate.
264
+ * @returns - True/false.
265
+ */
193
266
  static isValidNoteName(noteName) {
194
267
  return NoteNameRegex.test(noteName);
195
268
  }
269
+ /**
270
+ * Parse note name string to note props.
271
+ * @param noteName - Note name to parse.
272
+ * @returns - Parsed note props or undefined if parsing error.
273
+ */
196
274
  static parseNote(noteName) {
197
275
  var _a;
198
276
  let m = NoteNameRegex.exec(noteName);
@@ -206,6 +284,12 @@ var _Note = class _Note {
206
284
  let octave = octaveStr ? _Note.validateOctave(+octaveStr) : void 0;
207
285
  return { noteLetter, accidental, octave };
208
286
  }
287
+ /**
288
+ * Get scientific note name from given note name.
289
+ * @param noteName - Note name.
290
+ * @param symbolSet - Symbol set (ascii/unicode) for scientific note name.
291
+ * @returns - Scientific note name.
292
+ */
209
293
  static getScientificNoteName(noteName, symbolSet) {
210
294
  let p = _Note.parseNote(noteName);
211
295
  if (!p) {
@@ -214,9 +298,20 @@ var _Note = class _Note {
214
298
  let { noteLetter, accidental, octave } = p;
215
299
  return noteLetter + _Note.getAccidentalSymbol(accidental, symbolSet) + (octave != null ? octave : "");
216
300
  }
301
+ /**
302
+ * Get symbol of given accidental in given symbol set (ascii/unicide).
303
+ * @param accidental - Accidental.
304
+ * @param symbolsSet - Symbol set.
305
+ * @returns - Accidental symbol or undefined (invalid accidental).
306
+ */
217
307
  static getAccidentalSymbol(accidental, symbolsSet) {
218
308
  return symbolsSet === 1 /* Unicode */ ? AccidentalUnicodeSymbolMap.get(accidental) : AccidentalAsciiSymbolMap.get(accidental);
219
309
  }
310
+ /**
311
+ * Get accidental value from given accidental symbol.
312
+ * @param accidentalSymbol - Accidental symbol (e.g. "#").
313
+ * @returns - Accidental vlaue.
314
+ */
220
315
  static getAccidental(accidentalSymbol) {
221
316
  let accidental = AccidentalMap.get(accidentalSymbol);
222
317
  if (accidental === void 0) {
@@ -224,15 +319,32 @@ var _Note = class _Note {
224
319
  }
225
320
  return accidental;
226
321
  }
322
+ /**
323
+ * Get note letter from given diatonic id.
324
+ * @param diatonicId - Diatonic id.
325
+ * @returns - Note letter.
326
+ */
227
327
  static getNoteLetter(diatonicId) {
228
328
  return NoteLetters[_Note.getDiatonicClass(diatonicId)];
229
329
  }
330
+ /**
331
+ * Find next lowest possible diatonic id that is above given bottom level.
332
+ * @param diatonicId - Diatonic id to begin with.
333
+ * @param bottomDiatonicId - Bottom diatonic id.
334
+ * @param addOctaveIfEqual - If true then add one octave if diatonic id would equal to bottom diatonic id.
335
+ * @returns - Diatonic id.
336
+ */
230
337
  static findNextDiatonicIdAbove(diatonicId, bottomDiatonicId, addOctaveIfEqual) {
231
338
  let diatonicClass = _Note.getDiatonicClass(diatonicId);
232
339
  let bottomDiatonicClass = _Note.getDiatonicClass(bottomDiatonicId);
233
340
  let addOctave = addOctaveIfEqual ? diatonicClass <= bottomDiatonicClass ? 1 : 0 : diatonicClass < bottomDiatonicClass ? 1 : 0;
234
341
  return _Note.getDiatonicIdInOctave(diatonicClass, _Note.getOctaveFromDiatonicId(bottomDiatonicId) + addOctave);
235
342
  }
343
+ /**
344
+ * Validate if given argument is diatonic id.
345
+ * @param diatonicId - Diatonic id to validate.
346
+ * @returns - Valid diatonic id or throws.
347
+ */
236
348
  static validateDiatonicId(diatonicId) {
237
349
  if (Utils2.Is.isInteger(diatonicId)) {
238
350
  return diatonicId;
@@ -240,13 +352,23 @@ var _Note = class _Note {
240
352
  throw new MusicError2(MusicErrorType2.Note, `Invalid diatonicId: ${diatonicId}`);
241
353
  }
242
354
  }
355
+ /**
356
+ * Validate if given argument is diatonic class.
357
+ * @param diatonicClass - Diatonic class to validate.
358
+ * @returns - Valid diatonic class or throws.
359
+ */
243
360
  static validateDiatonicClass(diatonicClass) {
244
- if (Utils2.Is.isInteger(diatonicClass) && diatonicClass >= 0 && diatonicClass < 7) {
361
+ if (Utils2.Is.isIntegerBetween(diatonicClass, 0, 6)) {
245
362
  return diatonicClass;
246
363
  } else {
247
364
  throw new MusicError2(MusicErrorType2.Note, `Invalid diatonicClass: ${diatonicClass}`);
248
365
  }
249
366
  }
367
+ /**
368
+ * Validate if given argument is chromatic id.
369
+ * @param chromaticId - Chromatic id to validate.
370
+ * @returns - Valid chromatic id, or throws.
371
+ */
250
372
  static validateChromaticId(chromaticId) {
251
373
  if (Utils2.Is.isInteger(chromaticId)) {
252
374
  return chromaticId;
@@ -254,20 +376,35 @@ var _Note = class _Note {
254
376
  throw new MusicError2(MusicErrorType2.Note, `Invalid chromaticId: ${chromaticId}`);
255
377
  }
256
378
  }
379
+ /**
380
+ * Validate if given argument is chromatic class.
381
+ * @param chromaticClass - Chromatic class to validate.
382
+ * @returns - Valid chromatic class, or throws.
383
+ */
257
384
  static validatechromaticClass(chromaticClass) {
258
- if (Utils2.Is.isInteger(chromaticClass) && chromaticClass >= 0 && chromaticClass < 12) {
385
+ if (Utils2.Is.isIntegerBetween(chromaticClass, 0, 11)) {
259
386
  return chromaticClass;
260
387
  } else {
261
388
  throw new MusicError2(MusicErrorType2.Note, `Invalid chromaticClass: ${chromaticClass}`);
262
389
  }
263
390
  }
264
- static validateNoteLetter(note) {
265
- if (NoteLetters.some((n) => n === note)) {
266
- return note;
391
+ /**
392
+ * Validate if given argument if note letter.
393
+ * @param noteLetter - Note letter to validate.
394
+ * @returns - Valid note letter or throws.
395
+ */
396
+ static validateNoteLetter(noteLetter) {
397
+ if (NoteLetters.some((n) => n === noteLetter)) {
398
+ return noteLetter;
267
399
  } else {
268
- throw new MusicError2(MusicErrorType2.Note, `Invalid note: ${note}`);
400
+ throw new MusicError2(MusicErrorType2.Note, `Invalid note: ${noteLetter}`);
269
401
  }
270
402
  }
403
+ /**
404
+ * Validate if given argument is octave.
405
+ * @param octave - Octave to validate.
406
+ * @returns - Valid octave or throws.
407
+ */
271
408
  static validateOctave(octave) {
272
409
  if (Utils2.Is.isInteger(octave)) {
273
410
  return octave;
@@ -275,8 +412,13 @@ var _Note = class _Note {
275
412
  throw new MusicError2(MusicErrorType2.Note, `Invalid octave: ${octave}`);
276
413
  }
277
414
  }
415
+ /**
416
+ * Validate if given argument is valid accidental.
417
+ * @param acc - Accidental to validate.
418
+ * @returns - Valid accidental or thorws.
419
+ */
278
420
  static validateAccidental(acc) {
279
- if (Utils2.Is.isInteger(acc) && acc >= -2 && acc <= 2) {
421
+ if (Utils2.Is.isIntegerBetween(acc, -2, 2)) {
280
422
  return acc;
281
423
  } else {
282
424
  throw new MusicError2(MusicErrorType2.Note, `Invalid accidental: ${acc}`);
@@ -304,6 +446,12 @@ var _Note = class _Note {
304
446
  });
305
447
  return uniqueSet;
306
448
  }
449
+ /**
450
+ * Function to compare two notes using diatonic id and accidental properties of notes.
451
+ * @param a - Note a.
452
+ * @param b - Note b.
453
+ * @returns - -1, 0 or 1.
454
+ */
307
455
  static compareFunc(a, b) {
308
456
  if (a.diatonicId < b.diatonicId) {
309
457
  return -1;
@@ -425,6 +573,10 @@ var _KeySignature = class _KeySignature {
425
573
  }
426
574
  this.orderedAccidentedNotes = flats.length > 0 ? flats : sharps;
427
575
  }
576
+ /**
577
+ * Get accidental type sharps, flats, or natural (without accidentals).
578
+ * @returns - Accidental type.
579
+ */
428
580
  getAccidentalType() {
429
581
  if (this.orderedAccidentedNotes.length === 0) {
430
582
  return 0 /* Natural */;
@@ -434,23 +586,40 @@ var _KeySignature = class _KeySignature {
434
586
  return 2 /* Sharps */;
435
587
  }
436
588
  }
589
+ /**
590
+ * Get natural scale notes of this key signature, natural scale has 7 steps (e.g. Major scale: W, W, H, W, W, W, H).
591
+ * @returns - Array of notes.
592
+ */
437
593
  getNaturalScaleNotes() {
438
594
  return this.naturalScaleNotes;
439
595
  }
596
+ /**
597
+ * Get accidental for given diatonic id.
598
+ * @param diatonicId - Diatonic id.
599
+ * @returns - Accidental -2, -1, 0, 1 or 2.
600
+ */
440
601
  getAccidental(diatonicId) {
441
602
  var _a;
442
603
  return (_a = this.accidentalByDiatonicClass[Note.getDiatonicClass(diatonicId)]) != null ? _a : 0;
443
604
  }
605
+ /**
606
+ * Get number of accidentals this key signature has.
607
+ * @returns - Number of accidentals.
608
+ */
444
609
  getNumAccidentals() {
445
610
  return this.orderedAccidentedNotes.length;
446
611
  }
612
+ /**
613
+ * Get accidental notes in correct order.
614
+ * @returns - Array of accidental notes.
615
+ */
447
616
  getOrderedAccidentalNotes() {
448
617
  return this.orderedAccidentedNotes;
449
618
  }
450
619
  /**
451
- *
452
- * @param degree - number 1..7 or string e.g "b5" or "#4"
453
- * @returns
620
+ * Get note of key signature by degree value.
621
+ * @param degree - Degree number in range [1, 7] or string e.g "b5" or "#4".
622
+ * @returns - Note.
454
623
  */
455
624
  getNoteByDegree(degree) {
456
625
  let { deg, acc } = parseDegree(degree);
@@ -461,6 +630,12 @@ var _KeySignature = class _KeySignature {
461
630
  return new Note(note.diatonicId, note.accidental + acc);
462
631
  }
463
632
  }
633
+ /**
634
+ * Test equality of given key signatures.
635
+ * @param a - Key signature a.
636
+ * @param b - Key signature b.
637
+ * @returns - True/false.
638
+ */
464
639
  static equals(a, b) {
465
640
  if (a == null && b == null) {
466
641
  return true;
@@ -598,9 +773,13 @@ var Interval = class _Interval {
598
773
  constructor(note1, note2) {
599
774
  this.note1 = note1;
600
775
  this.note2 = note2;
776
+ /** Interval direction. */
601
777
  __publicField(this, "direction");
778
+ /** Number of semitones. */
602
779
  __publicField(this, "semitones");
780
+ /** Interval quantity. */
603
781
  __publicField(this, "quantity");
782
+ /** Interval quality. */
604
783
  __publicField(this, "quality");
605
784
  if (note2.diatonicId >= note1.diatonicId) {
606
785
  this.direction = note2.diatonicId === note1.diatonicId ? "Unison" : "Ascending";
@@ -618,6 +797,12 @@ var Interval = class _Interval {
618
797
  throw new InvalidIntervalException("Unknown interval quality");
619
798
  }
620
799
  }
800
+ /**
801
+ * Get interval between given two notes.
802
+ * @param note1 - First note.
803
+ * @param note2 - Second note.
804
+ * @returns - Interval if valid, or undefined.
805
+ */
621
806
  static get(note1, note2) {
622
807
  try {
623
808
  return new _Interval(note1, note2);
@@ -629,12 +814,20 @@ var Interval = class _Interval {
629
814
  }
630
815
  }
631
816
  }
817
+ /**
818
+ * Get string presentation of interval (e.g. "Descending Major 2").
819
+ * @returns - Interval string.
820
+ */
632
821
  toString() {
633
822
  let direction = this.direction === "Unison" ? "" : this.direction + " ";
634
823
  let quality = this.quality + " ";
635
824
  let quantity = this.direction === "Unison" ? "Unison" : formatQuantity(this.quantity);
636
825
  return direction + quality + quantity;
637
826
  }
827
+ /**
828
+ * Get abbrevated string presentation of interval (e.g. "↓M2").
829
+ * @returns - Interval abbrevated string.
830
+ */
638
831
  toAbbrString() {
639
832
  var _a;
640
833
  let direction = this.direction === "Descending" ? "\u2193" : "";
@@ -729,12 +922,20 @@ function getMode(scaleType) {
729
922
  }
730
923
  }
731
924
  var Scale = class extends KeySignature {
925
+ /**
926
+ * Create nev scale object instance.
927
+ * @param tonic - Tonic (e.g. "C" in "C Major").
928
+ * @param scaleType - Scale typo ("e.g. "Major" in "C Major").
929
+ */
732
930
  constructor(tonic, scaleType) {
733
931
  super(tonic, getMode(scaleType));
734
932
  this.tonic = tonic;
735
933
  this.scaleType = scaleType;
934
+ /** Degrees of scale notes. */
736
935
  __publicField(this, "scaleDegrees");
936
+ /** Scale notes. */
737
937
  __publicField(this, "scaleNotes");
938
+ /** Degrees (or undefined) of chromatic classes. */
738
939
  __publicField(this, "chromaticClassDegree");
739
940
  __publicField(this, "preferredChromaticNoteCache", /* @__PURE__ */ new Map());
740
941
  switch (scaleType) {
@@ -766,6 +967,12 @@ var Scale = class extends KeySignature {
766
967
  return id >= 0 ? this.scaleDegrees[id] : void 0;
767
968
  });
768
969
  }
970
+ /**
971
+ * Compare if two scales equals.
972
+ * @param a - Scale a.
973
+ * @param b - Scale b.
974
+ * @returns - Boolean equality of given scales.
975
+ */
769
976
  static equals(a, b) {
770
977
  if (a == null && b == null) {
771
978
  return true;
@@ -775,6 +982,11 @@ var Scale = class extends KeySignature {
775
982
  return a === b || a.getScaleName() === b.getScaleName();
776
983
  }
777
984
  }
985
+ /**
986
+ * Get scale name.
987
+ * @param symbolSet - Symbol set to format scale name in ascii or unicode.
988
+ * @returns - Scale name string.
989
+ */
778
990
  getScaleName(symbolSet) {
779
991
  switch (symbolSet) {
780
992
  case 1 /* Unicode */:
@@ -783,6 +995,12 @@ var Scale = class extends KeySignature {
783
995
  return this.tonic + " " + this.scaleType;
784
996
  }
785
997
  }
998
+ /**
999
+ * Get scale notes.
1000
+ * @param bottomNote - Computed scale notes begin no lower than this note.
1001
+ * @param numOctaves - How many octaves?
1002
+ * @returns - Array of scale notes.
1003
+ */
786
1004
  getScaleNotes(bottomNote, numOctaves) {
787
1005
  if (!Utils5.Is.isIntegerGte(numOctaves, 1)) {
788
1006
  throw new MusicError5(MusicErrorType5.Scale, `Invalid numOctaves: ${numOctaves}`);
@@ -798,9 +1016,17 @@ var Scale = class extends KeySignature {
798
1016
  return new Note(diatonicId, note.accidental);
799
1017
  });
800
1018
  }
1019
+ /**
1020
+ * Get scale overview (e.g. "C - D - E - F - G - A - B" for "C Major" scale).
1021
+ * @returns - Scale overview string.
1022
+ */
801
1023
  getScaleOverview() {
802
1024
  return this.getScaleNotes("C4", 1).map((note) => note.formatOmitOctave(1 /* Unicode */)).join(" - ");
803
1025
  }
1026
+ /**
1027
+ * Get scale steps, array of 1 (half step) and 2 (whole step), (e.g. [2, 2, 1, 2, 2, 2, 1] for Major scale).
1028
+ * @returns - Array of scale steps.
1029
+ */
804
1030
  getScaleSteps() {
805
1031
  let chromaticIds = this.getScaleNotes("C4", 1).map((note) => note.chromaticId);
806
1032
  let steps = [];
@@ -809,15 +1035,34 @@ var Scale = class extends KeySignature {
809
1035
  }
810
1036
  return steps;
811
1037
  }
1038
+ /**
1039
+ * Get scale steps string presentation, array of "H" (half step) and "W" (whole step), (e.g. ["W", "W", "H", "W", "W", "W", "H"] for Major scale).
1040
+ * @returns - Array of scale steps string presentation.
1041
+ */
812
1042
  getScaleStringSteps() {
813
1043
  return this.getScaleSteps().map((step) => step === 1 ? "H" : step === 2 ? "W" : step.toString() + "H");
814
1044
  }
1045
+ /**
1046
+ * Test if given note is scale note.
1047
+ * @param note - Note to test.
1048
+ * @returns - True/false.
1049
+ */
815
1050
  isScaleNote(note) {
816
1051
  return this.chromaticClassDegree[note.chromaticClass] !== void 0;
817
1052
  }
1053
+ /**
1054
+ * Test if given note is scale root note.
1055
+ * @param note - Note to test.
1056
+ * @returns - True/false.
1057
+ */
818
1058
  isScaleRootNote(note) {
819
1059
  return String(this.chromaticClassDegree[note.chromaticClass]) === "1";
820
1060
  }
1061
+ /**
1062
+ * Get interval value between scale root note and given note.
1063
+ * @param note - Note.
1064
+ * @returns - Interval.
1065
+ */
821
1066
  getIntervalFromRootNote(note) {
822
1067
  let rootNote = this.getScaleNotes("C0", 1)[0];
823
1068
  while (note.chromaticId >= rootNote.chromaticId + 12) {
@@ -833,6 +1078,11 @@ var Scale = class extends KeySignature {
833
1078
  return interval;
834
1079
  }
835
1080
  }
1081
+ /**
1082
+ * Get preferred chromatic note from given chromatic id.
1083
+ * @param chromaticId - Chromatic id.
1084
+ * @returns - Note.
1085
+ */
836
1086
  getPreferredChromaticNote(chromaticId) {
837
1087
  Note.validateChromaticId(chromaticId);
838
1088
  let note = this.preferredChromaticNoteCache.get(chromaticId);
@@ -875,6 +1125,10 @@ var Scale = class extends KeySignature {
875
1125
  }
876
1126
  };
877
1127
  var ScaleFactory = class {
1128
+ /**
1129
+ * Create new scale factory object instance.
1130
+ * @param type - Scale type.
1131
+ */
878
1132
  constructor(type) {
879
1133
  this.type = type;
880
1134
  __publicField(this, "tonicList", []);
@@ -920,15 +1174,32 @@ var ScaleFactory = class {
920
1174
  ...flatScales.sort(SortByAccidentalCountFunc).map((scale) => scale.tonic)
921
1175
  ];
922
1176
  }
1177
+ /**
1178
+ * Get list of tonics (e.g. "C", "C#", ... for Major scale type).
1179
+ * @returns - Array of tonics.
1180
+ */
923
1181
  getTonicList() {
924
1182
  return this.tonicList;
925
1183
  }
1184
+ /**
1185
+ * Get default tonic.
1186
+ * @returns - Default tonic.
1187
+ */
926
1188
  getDefaultTonic() {
927
1189
  return this.tonicList[0];
928
1190
  }
1191
+ /**
1192
+ * Get scale type.
1193
+ * @returns - SCale type.
1194
+ */
929
1195
  getType() {
930
1196
  return this.type;
931
1197
  }
1198
+ /**
1199
+ * Get scale by given tonic.
1200
+ * @param tonic - Tonic (e.g. "C").
1201
+ * @returns - Scale.
1202
+ */
932
1203
  getScale(tonic) {
933
1204
  let scale = this.scaleMap.get(tonic);
934
1205
  if (!scale) {
@@ -937,6 +1208,11 @@ var ScaleFactory = class {
937
1208
  return scale;
938
1209
  }
939
1210
  }
1211
+ /**
1212
+ * Test if this scale factory has scale for given tonic value.
1213
+ * @param tonic - Tonic.
1214
+ * @returns - True/false.
1215
+ */
940
1216
  hasScale(tonic) {
941
1217
  return this.scaleMap.get(tonic) !== void 0;
942
1218
  }
@@ -982,10 +1258,19 @@ function validateScaleType(scaleType) {
982
1258
  if (Utils5.Is.isEnumValue(scaleType, ScaleType)) {
983
1259
  return scaleType;
984
1260
  } else {
985
- throw new MusicError5(MusicErrorType5.Scale, `Invalid scaleType: ${scaleType}`);
1261
+ throw new MusicError5(MusicErrorType5.Scale, `Invalid scaleType: "${scaleType}"`);
986
1262
  }
987
1263
  }
988
- function getScale(tonic, scaleType) {
1264
+ function getScale(arg0, arg1) {
1265
+ let tonic;
1266
+ let scaleType;
1267
+ if (arg1 !== void 0) {
1268
+ tonic = arg0;
1269
+ scaleType = validateScaleType(arg1);
1270
+ } else {
1271
+ tonic = arg0.split(" ")[0];
1272
+ scaleType = validateScaleType(arg0.substring(tonic.length + 1));
1273
+ }
989
1274
  return getScaleFactory(scaleType).getScale(tonic);
990
1275
  }
991
1276
  var DefaultScale = getScale("C", "Major" /* Major */);
@@ -1092,9 +1377,13 @@ var Chord = class _Chord {
1092
1377
  constructor(chordInfo, chordNotes, rootNote, bassNote) {
1093
1378
  this.chordInfo = chordInfo;
1094
1379
  this.rootNote = rootNote;
1380
+ /** Chord name. */
1095
1381
  __publicField(this, "name");
1382
+ /** Notes of this chord. */
1096
1383
  __publicField(this, "notes");
1384
+ /** Notes that are omitted in this chord (partial chord). */
1097
1385
  __publicField(this, "omitNotes");
1386
+ /** Bass note if not chord root note (e.g. "B" in "C/B"). */
1098
1387
  __publicField(this, "slashBassNote");
1099
1388
  this.name = chordInfo.name;
1100
1389
  let notesLeft = chordNotes.slice();
@@ -1133,6 +1422,11 @@ var Chord = class _Chord {
1133
1422
  throw new InvalidChordException("Power chord no bass note allowed!");
1134
1423
  }
1135
1424
  }
1425
+ /**
1426
+ * Get all chords that can be made up with given notes.
1427
+ * @param notes - Array of notes.
1428
+ * @returns - Array of chords.
1429
+ */
1136
1430
  static getChords(notes) {
1137
1431
  let chords = [];
1138
1432
  let chordNotes = Note.sort(notes);
@@ -1172,7 +1466,8 @@ var Chord = class _Chord {
1172
1466
  return true;
1173
1467
  }
1174
1468
  /**
1175
- * @returns Chord name e.g. "C/B"
1469
+ * Get chord string presentation.
1470
+ * @returns Chord string presentation (e.g. "C/B").
1176
1471
  */
1177
1472
  toString() {
1178
1473
  let symbolSet = 1 /* Unicode */;
@@ -1181,7 +1476,8 @@ var Chord = class _Chord {
1181
1476
  return rootNoteStr + this.name + slashBassStr;
1182
1477
  }
1183
1478
  /**
1184
- * @returns Degree notation string, e.g. "E - 1(C) - 3(E) - 5(G)"
1479
+ * Get degree notation string (e.g. "E - 1(C) - 3(E) - 5(G)").
1480
+ * @returns Degree notation string.
1185
1481
  */
1186
1482
  getDegreeNotationString() {
1187
1483
  let symbolSet = 1 /* Unicode */;
@@ -1192,7 +1488,8 @@ var Chord = class _Chord {
1192
1488
  return bassNoteStr + degreeNoteStr;
1193
1489
  }
1194
1490
  /**
1195
- * @returns Omitted degrees string e.g. "Omits 5(G), 9(D)"
1491
+ * Get string presentation of omitted degrees (e.g. "Omits 5(G), 9(D)").
1492
+ * @returns - String presentation of omitted degrees.
1196
1493
  */
1197
1494
  getOmittedDegreesString() {
1198
1495
  let omittedStrList = this.omitNotes.map((omit, i) => {
@@ -1201,15 +1498,17 @@ var Chord = class _Chord {
1201
1498
  return omittedStrList.length > 0 ? "Omits " + omittedStrList.join(", ") : "";
1202
1499
  }
1203
1500
  /**
1204
- * @param i - Degree index
1205
- * @returns Degree string for given degree index, e.g. "3"
1501
+ * Degree index for given degree index (e.g. "3").
1502
+ * @param i - Chord degree index (e.g. 0 is first note degree of chord).
1503
+ * @returns Degree string.
1206
1504
  */
1207
1505
  getDegreeStr(i) {
1208
1506
  return Note.replaceAccidentalSymbols("" + this.chordInfo.degrees[i], 1 /* Unicode */);
1209
1507
  }
1210
1508
  /**
1211
- * @param i - Degree index
1212
- * @returns Note string for given degree index, e.g. "E"
1509
+ * Get note name for given degree index (e.g. "E").
1510
+ * @param i - Chord degree index (e.g. 0 is first note degree of chord).
1511
+ * @returns - Note name string.
1213
1512
  */
1214
1513
  getNoteStr(i) {
1215
1514
  return this.notes[i].formatOmitOctave(1 /* Unicode */);
@@ -1476,10 +1775,10 @@ var Handedness = /* @__PURE__ */ ((Handedness2) => {
1476
1775
  })(Handedness || {});
1477
1776
  var DefaultHandedness = 0 /* RightHanded */;
1478
1777
  function validateHandedness(h) {
1479
- if (!Utils7.Is.isEnumValue(h, Handedness)) {
1480
- throw new MusicError7(MusicErrorType7.InvalidArg, `Invalid handedness: ${h}`);
1481
- } else {
1778
+ if (Utils7.Is.isEnumValue(h, Handedness)) {
1482
1779
  return h;
1780
+ } else {
1781
+ throw new MusicError7(MusicErrorType7.InvalidArg, `Invalid handedness: ${h}`);
1483
1782
  }
1484
1783
  }
1485
1784
  var TuningNameList = tunings_default.list.map((data) => data.name);
@@ -1514,92 +1813,268 @@ import { Utils as Utils9 } from "@tspro/ts-utils-lib";
1514
1813
  // src/theory/rhythm.ts
1515
1814
  import { Utils as Utils8 } from "@tspro/ts-utils-lib";
1516
1815
  import { MusicError as MusicError8, MusicErrorType as MusicErrorType8 } from "@tspro/web-music-score/core";
1816
+ var cmp = (a, b) => a === b ? 0 : a < b ? -1 : 1;
1817
+ var MaxTupletRatioValue = 12;
1818
+ var TicksMultiplier = 12 * 11 * 9 * 7 * 5;
1517
1819
  var NoteLength = /* @__PURE__ */ ((NoteLength3) => {
1518
- NoteLength3[NoteLength3["Whole"] = 192] = "Whole";
1519
- NoteLength3[NoteLength3["Half"] = 96] = "Half";
1520
- NoteLength3[NoteLength3["Quarter"] = 48] = "Quarter";
1521
- NoteLength3[NoteLength3["Eighth"] = 24] = "Eighth";
1522
- NoteLength3[NoteLength3["Sixteenth"] = 12] = "Sixteenth";
1523
- NoteLength3[NoteLength3["ThirtySecond"] = 6] = "ThirtySecond";
1524
- NoteLength3[NoteLength3["SixtyFourth"] = 3] = "SixtyFourth";
1820
+ NoteLength3["Whole"] = "1n";
1821
+ NoteLength3["WholeTriplet"] = "1t";
1822
+ NoteLength3["WholeDot"] = "1.";
1823
+ NoteLength3["Whole2Dots"] = "1..";
1824
+ NoteLength3["Whole12Dots"] = "1..";
1825
+ NoteLength3["Whole3Dots"] = "1...";
1826
+ NoteLength3["Whole4Dots"] = "1....";
1827
+ NoteLength3["Whole5Dots"] = "1.....";
1828
+ NoteLength3["Whole6Dots"] = "1......";
1829
+ NoteLength3["Half"] = "2n";
1830
+ NoteLength3["HalfTriplet"] = "2t";
1831
+ NoteLength3["HalfDot"] = "2.";
1832
+ NoteLength3["Half2Dots"] = "2..";
1833
+ NoteLength3["Half3Dots"] = "2...";
1834
+ NoteLength3["Half4Dots"] = "2....";
1835
+ NoteLength3["Half5Dots"] = "2.....";
1836
+ NoteLength3["Quarter"] = "4n";
1837
+ NoteLength3["QuarterTriplet"] = "4t";
1838
+ NoteLength3["QuarterDot"] = "4.";
1839
+ NoteLength3["Quarter2Dots"] = "4..";
1840
+ NoteLength3["Quarter3Dots"] = "4...";
1841
+ NoteLength3["Quarter4Dots"] = "4....";
1842
+ NoteLength3["Eighth"] = "8n";
1843
+ NoteLength3["EighthTriplet"] = "8t";
1844
+ NoteLength3["EighthDot"] = "8.";
1845
+ NoteLength3["Eighth2Dots"] = "8..";
1846
+ NoteLength3["Eighth3Dots"] = "8...";
1847
+ NoteLength3["Sixteenth"] = "16n";
1848
+ NoteLength3["SixteenthTriplet"] = "16t";
1849
+ NoteLength3["SixteenthDot"] = "16.";
1850
+ NoteLength3["Sixteenth2Dots"] = "16..";
1851
+ NoteLength3["ThirtySecond"] = "32n";
1852
+ NoteLength3["ThirtySecondTriplet"] = "32t";
1853
+ NoteLength3["ThirtySecondDot"] = "32.";
1854
+ NoteLength3["SixtyFourth"] = "64n";
1855
+ NoteLength3["SixtyFourthTriplet"] = "64t";
1525
1856
  return NoteLength3;
1526
1857
  })(NoteLength || {});
1527
- var MaxNoteLength = 192 /* Whole */;
1528
- var MinNoteLength = 3 /* SixtyFourth */;
1529
- var FlagCountMap = /* @__PURE__ */ new Map([
1530
- [192 /* Whole */, 0],
1531
- [96 /* Half */, 0],
1532
- [48 /* Quarter */, 0],
1533
- [24 /* Eighth */, 1],
1534
- [12 /* Sixteenth */, 2],
1535
- [6 /* ThirtySecond */, 3],
1536
- [3 /* SixtyFourth */, 4]
1537
- ]);
1538
- var NoteSymbolMap = /* @__PURE__ */ new Map([
1539
- [192 /* Whole */, "\u{1D15D}"],
1540
- [96 /* Half */, "\u{1D15E}"],
1541
- [48 /* Quarter */, "\u{1D15F}"],
1542
- [24 /* Eighth */, "\u{1D160}"],
1543
- [12 /* Sixteenth */, "\u{1D161}"],
1544
- [6 /* ThirtySecond */, "\u{1D162}"],
1545
- [3 /* SixtyFourth */, "\u{1D163}"]
1546
- ]);
1547
1858
  function validateNoteLength(noteLength) {
1548
- if (!Utils8.Is.isEnumValue(noteLength, NoteLength)) {
1859
+ if (Utils8.Is.isEnumValue(noteLength, NoteLength)) {
1860
+ return noteLength;
1861
+ } else {
1549
1862
  throw new MusicError8(MusicErrorType8.InvalidArg, `Invalid noteLength: ${noteLength}`);
1863
+ }
1864
+ }
1865
+ var _NoteLengthProps = class _NoteLengthProps {
1866
+ constructor(noteLength) {
1867
+ /** Note length. */
1868
+ __publicField(this, "noteLength");
1869
+ /** Note size (whole=1, half=2, quarter=4, ...). */
1870
+ __publicField(this, "noteSize");
1871
+ /** Number of ticks (not altered by isTriplet). */
1872
+ __publicField(this, "ticks");
1873
+ /** Flag count. */
1874
+ __publicField(this, "flagCount");
1875
+ /** Dot count. */
1876
+ __publicField(this, "dotCount");
1877
+ /** Max dot count. */
1878
+ __publicField(this, "maxDotCount");
1879
+ /** Is triplet? */
1880
+ __publicField(this, "isTriplet");
1881
+ /** Has note stem. */
1882
+ __publicField(this, "hasStem");
1883
+ /** Is note head solid (black)? */
1884
+ __publicField(this, "isSolid");
1885
+ this.noteLength = validateNoteLength(noteLength);
1886
+ this.noteSize = parseInt(noteLength);
1887
+ this.isTriplet = noteLength.endsWith("t");
1888
+ this.maxDotCount = this.isTriplet ? 0 : Math.floor(Math.log2(_NoteLengthProps.ShortestNoteSize / this.noteSize));
1889
+ this.dotCount = Utils8.Str.charCount(noteLength, ".");
1890
+ this.flagCount = this.noteSize > 4 ? Math.floor(Math.log2(this.noteSize / 4)) : 0;
1891
+ this.ticks = TicksMultiplier * _NoteLengthProps.ShortestNoteSize / this.noteSize;
1892
+ this.hasStem = this.noteSize > 1;
1893
+ this.isSolid = this.noteSize > 2;
1894
+ if (this.dotCount > this.maxDotCount) {
1895
+ throw new MusicError8(MusicErrorType8.Note, `dotCount ${this.dotCount} > maxDotCount ${this.maxDotCount}, for noteLength "${this.noteLength}".`);
1896
+ } else if (this.isTriplet && this.dotCount > 0) {
1897
+ throw new MusicError8(MusicErrorType8.Note, `noteLength "${this.noteLength}" is both triplet and dotted!`);
1898
+ }
1899
+ }
1900
+ /**
1901
+ * Get note length props.
1902
+ * @param noteLength - Note length.
1903
+ * @returns - Note length props.
1904
+ */
1905
+ static get(noteLength) {
1906
+ let p = this.cache.get(noteLength);
1907
+ if (!p) {
1908
+ this.cache.set(noteLength, p = new _NoteLengthProps(noteLength));
1909
+ }
1910
+ return p;
1911
+ }
1912
+ /**
1913
+ * Create note length props.
1914
+ * @param noteLength - Note length or note size.
1915
+ * @param dotCount - Dot count.
1916
+ * @returns - Note length props.
1917
+ */
1918
+ static create(noteLength, dotCount = 0) {
1919
+ let noteSize = typeof noteLength === "number" ? noteLength : this.get(noteLength).noteSize;
1920
+ return this.get(noteSize + (Utils8.Is.isIntegerGte(dotCount, 1) ? ".".repeat(dotCount) : "n"));
1921
+ }
1922
+ /**
1923
+ * Compare note lengths/sizes. Whole (1) > half (2) > quarter (4), etc.
1924
+ * Ignores possible triplet property of note length.
1925
+ * @param a - NoteLengthProps, NoteLength/Str or noteSize
1926
+ * @param b - NoteLengthProps, NoteLength/Str or noteSize
1927
+ * @returns - -1: a < b, 0: a === b, +1: a > b (note length/size comparisons)
1928
+ */
1929
+ static cmp(a, b) {
1930
+ let aNoteSize = a instanceof _NoteLengthProps ? a.noteSize : typeof a === "number" ? a : _NoteLengthProps.get(a).noteSize;
1931
+ let bNoteSize = b instanceof _NoteLengthProps ? b.noteSize : typeof b === "number" ? b : _NoteLengthProps.get(b).noteSize;
1932
+ return cmp(bNoteSize, aNoteSize);
1933
+ }
1934
+ /**
1935
+ * Compare note lengths/sizes for equality.
1936
+ * Ignores possible triplet property of note length.
1937
+ * @param a - NoteLengthProps, NoteLength/Str or noteSize
1938
+ * @param b - NoteLengthProps, NoteLength/Str or noteSize
1939
+ * @returns - true: a === b, false: a !== b (note length/size comparisons)
1940
+ */
1941
+ static equals(a, b) {
1942
+ let aNoteSize = a instanceof _NoteLengthProps ? a.noteSize : typeof a === "number" ? a : _NoteLengthProps.get(a).noteSize;
1943
+ let bNoteSize = b instanceof _NoteLengthProps ? b.noteSize : typeof b === "number" ? b : _NoteLengthProps.get(b).noteSize;
1944
+ return aNoteSize === bNoteSize;
1945
+ }
1946
+ };
1947
+ /** Longest note size (e.g. 1 = whole note). */
1948
+ __publicField(_NoteLengthProps, "LongestNoteSize", Math.min(...Utils8.Enum.getEnumValues(NoteLength).map((noteLength) => parseInt(noteLength))));
1949
+ /** Shortest note size (e.g. 64 = sixtyfourth note). */
1950
+ __publicField(_NoteLengthProps, "ShortestNoteSize", Math.max(...Utils8.Enum.getEnumValues(NoteLength).map((noteLength) => parseInt(noteLength))));
1951
+ __publicField(_NoteLengthProps, "cache", /* @__PURE__ */ new Map());
1952
+ var NoteLengthProps = _NoteLengthProps;
1953
+ function validateTupletRatio(tupletRatio) {
1954
+ if (Utils8.Is.isObject(tupletRatio) && Utils8.Is.isIntegerBetween(tupletRatio.parts, 2, MaxTupletRatioValue) && Utils8.Is.isIntegerBetween(tupletRatio.inTimeOf, 2, MaxTupletRatioValue)) {
1955
+ return tupletRatio;
1550
1956
  } else {
1551
- return noteLength;
1957
+ throw new MusicError8(MusicErrorType8.Note, `Invalid tupletRatio ${JSON.stringify(tupletRatio)}`);
1552
1958
  }
1553
1959
  }
1554
- var RhythmProps = class _RhythmProps {
1555
- constructor(noteLength, dotted, triplet) {
1960
+ var Tuplet = {
1961
+ /** Duplet: 2 in the time of 3 */
1962
+ Duplet: { parts: 2, inTimeOf: 3 },
1963
+ /** Triplet: 3 in the time of 2 */
1964
+ Triplet: { parts: 3, inTimeOf: 2 },
1965
+ /** Quadruplet: 4 in the time of 3 */
1966
+ Quadruplet: { parts: 4, inTimeOf: 3 }
1967
+ };
1968
+ var _RhythmProps = class _RhythmProps {
1969
+ constructor(noteLength, dotCount, tupletRatio) {
1970
+ /** Note length. */
1556
1971
  __publicField(this, "noteLength");
1557
- __publicField(this, "dotted");
1558
- __publicField(this, "triplet");
1972
+ /** Note size (whole=1, half=2, quarter=4, ...). */
1973
+ __publicField(this, "noteSize");
1974
+ /** Dot count. */
1975
+ __publicField(this, "dotCount");
1976
+ /** Tuplet ratio. */
1977
+ __publicField(this, "tupletRatio");
1978
+ /** Number of ticks. */
1559
1979
  __publicField(this, "ticks");
1980
+ /** Flag count. */
1560
1981
  __publicField(this, "flagCount");
1561
- var _a;
1982
+ /** Has note stem. */
1983
+ __publicField(this, "hasStem");
1984
+ /** Is note head solid (black)? */
1985
+ __publicField(this, "isSolidNoteHead");
1562
1986
  this.noteLength = validateNoteLength(noteLength);
1563
- this.dotted = dotted === true;
1564
- this.triplet = triplet === true;
1565
- this.ticks = this.noteLength;
1566
- this.flagCount = (_a = FlagCountMap.get(this.noteLength)) != null ? _a : 0;
1567
- if (this.dotted && this.triplet) {
1568
- throw new MusicError8(MusicErrorType8.Note, "Note cannot be both dotted and triplet!");
1569
- } else if (this.dotted && this.noteLength === MinNoteLength) {
1570
- throw new MusicError8(MusicErrorType8.Note, "Shortest note cannot be dotted!");
1987
+ let p = NoteLengthProps.get(noteLength);
1988
+ this.noteSize = p.noteSize;
1989
+ this.ticks = p.ticks;
1990
+ this.flagCount = p.flagCount;
1991
+ this.dotCount = dotCount != null ? dotCount : p.dotCount;
1992
+ this.hasStem = p.hasStem;
1993
+ this.isSolidNoteHead = p.isSolid;
1994
+ if (Utils8.Is.isObject(tupletRatio)) {
1995
+ this.tupletRatio = validateTupletRatio(tupletRatio);
1996
+ } else if (p.isTriplet) {
1997
+ this.tupletRatio = Tuplet.Triplet;
1998
+ } else {
1999
+ this.tupletRatio = void 0;
2000
+ }
2001
+ if (this.dotCount > 0 && this.tupletRatio !== void 0) {
2002
+ throw new MusicError8(MusicErrorType8.Note, `Note cannot be both dotted and tuplet!`);
2003
+ } else if (this.dotCount > p.maxDotCount) {
2004
+ throw new MusicError8(MusicErrorType8.Note, `Too big dot count ${this.dotCount} for note length ${this.noteLength}.`);
1571
2005
  }
1572
- if (this.dotted) {
1573
- this.ticks += this.noteLength / 2;
2006
+ for (let add = this.ticks / 2, i = 1; i <= this.dotCount; i++, add /= 2) {
2007
+ this.ticks += add;
1574
2008
  }
1575
- if (this.triplet) {
1576
- this.ticks = this.ticks * 2 / 3;
2009
+ if (this.tupletRatio) {
2010
+ this.ticks *= this.tupletRatio.inTimeOf / this.tupletRatio.parts;
1577
2011
  }
1578
2012
  }
1579
- static createFromNoteSize(noteSize) {
1580
- return new _RhythmProps(MaxNoteLength / noteSize);
2013
+ /**
2014
+ * Get string presentation of rhythm props.
2015
+ * @returns - String presentation.
2016
+ */
2017
+ toString() {
2018
+ let sym = _RhythmProps.NoteSymbolMap.get(this.noteSize);
2019
+ let dots = ".".repeat(this.dotCount);
2020
+ return sym ? sym + dots : "" + this.noteSize + (dots.length > 0 ? dots : "n");
1581
2021
  }
1582
- canDot() {
1583
- return !this.dotted && this.noteLength !== MinNoteLength;
2022
+ /**
2023
+ * Get rhythm props with given arguments.
2024
+ * @param noteLength - Note length.
2025
+ * @param dotCount - Dot count.
2026
+ * @param tupletRatio - Tuplet ratio.
2027
+ * @returns - Rhythm props.
2028
+ */
2029
+ static get(noteLength, dotCount, tupletRatio) {
2030
+ if (dotCount !== void 0 || tupletRatio !== void 0) {
2031
+ return new _RhythmProps(noteLength, dotCount, tupletRatio);
2032
+ } else {
2033
+ let rhythmProps = this.cache.get(noteLength);
2034
+ if (!rhythmProps) {
2035
+ this.cache.set(noteLength, rhythmProps = new _RhythmProps(noteLength));
2036
+ }
2037
+ return rhythmProps;
2038
+ }
1584
2039
  }
1585
- hasStem() {
1586
- return this.noteLength < 192 /* Whole */;
2040
+ /**
2041
+ * Compare duration of rhythm props.
2042
+ * @param a - RhythmProps
2043
+ * @param b - RhythmProps
2044
+ * @returns - -1: a < b, 0: a === b, +1: a > b (duration comparisons)
2045
+ */
2046
+ static cmp(a, b) {
2047
+ return cmp(a.ticks, b.ticks);
1587
2048
  }
1588
- toString() {
1589
- return NoteSymbolMap.get(this.noteLength) + (this.dotted ? "." : "");
2049
+ /**
2050
+ * Compare duration equality of rhythm props.
2051
+ * @param a - RhythmProps
2052
+ * @param b - RhythmProps
2053
+ * @returns - true: a === b, false: a !== b (duration comparisons)
2054
+ */
2055
+ static equals(a, b) {
2056
+ return a.ticks === b.ticks;
1590
2057
  }
1591
2058
  };
2059
+ __publicField(_RhythmProps, "NoteSymbolMap", /* @__PURE__ */ new Map([[1, "\u{1D15D}"], [2, "\u{1D15E}"], [4, "\u{1D15F}"], [8, "\u{1D160}"], [16, "\u{1D161}"], [32, "\u{1D162}"], [64, "\u{1D163}"], [128, "\u{1D164}"]]));
2060
+ __publicField(_RhythmProps, "cache", /* @__PURE__ */ new Map());
2061
+ var RhythmProps = _RhythmProps;
1592
2062
 
1593
2063
  // src/theory/time-signature.ts
1594
2064
  import { MusicError as MusicError9, MusicErrorType as MusicErrorType9 } from "@tspro/web-music-score/core";
1595
2065
  var TimeSignature = class {
1596
2066
  constructor(...args) {
2067
+ /** Number of beats in measure, upper value (e.g. "3" in "3/4"). */
1597
2068
  __publicField(this, "beatCount");
2069
+ /** Beat size of time signature, lower value (e.g. "4" in "3/4"). */
1598
2070
  __publicField(this, "beatSize");
1599
- /** Lengths in ticks */
2071
+ /** Beat length. */
1600
2072
  __publicField(this, "beatLength");
2073
+ /** Number of ticks in measure. */
1601
2074
  __publicField(this, "measureTicks");
2075
+ /** Number of beam groups in measure. */
1602
2076
  __publicField(this, "beamGroupCount");
2077
+ /** Length of one beam group. */
1603
2078
  __publicField(this, "beamGroupLength");
1604
2079
  if (args.length === 1 && typeof args[0] === "string") {
1605
2080
  let parts = args[0].split("/");
@@ -1616,9 +2091,9 @@ var TimeSignature = class {
1616
2091
  } else if (!Utils9.Is.isIntegerGte(this.beatSize, 1)) {
1617
2092
  throw new MusicError9(MusicErrorType9.Timesignature, `Invalid beatSize: ${this.beatSize}`);
1618
2093
  }
1619
- let beatLengthValue = RhythmProps.createFromNoteSize(this.beatSize);
1620
- this.beatLength = beatLengthValue.noteLength;
1621
- this.measureTicks = this.beatCount * beatLengthValue.ticks;
2094
+ let props = NoteLengthProps.create(this.beatSize);
2095
+ this.beatLength = props.noteLength;
2096
+ this.measureTicks = this.beatCount * props.ticks;
1622
2097
  if (this.is(2, 4) || this.is(3, 4) || this.is(4, 4)) {
1623
2098
  this.beamGroupCount = this.beatCount;
1624
2099
  } else if (this.is(6, 8) || this.is(9, 8)) {
@@ -1632,9 +2107,19 @@ var TimeSignature = class {
1632
2107
  throw new MusicError9(MusicErrorType9.Timesignature, `Invalid beamGroupLength: ${this.beamGroupLength}`);
1633
2108
  }
1634
2109
  }
2110
+ /**
2111
+ * Test whether this time signature has given beat count and size.
2112
+ * @param beatCount - Beat count.
2113
+ * @param beatSize - Beat size.
2114
+ * @returns - Boolean whether this time signature match given beat count and size.
2115
+ */
1635
2116
  is(beatCount, beatSize) {
1636
2117
  return this.beatCount === beatCount && this.beatSize === beatSize;
1637
2118
  }
2119
+ /**
2120
+ * Get string representation of this time signature (e.g. "3/4").
2121
+ * @returns - String representation.
2122
+ */
1638
2123
  toString() {
1639
2124
  return this.beatCount + "/" + this.beatSize;
1640
2125
  }
@@ -1651,17 +2136,17 @@ function getDefaultTimeSignature() {
1651
2136
  var defaultTempo;
1652
2137
  function getDefaultTempo() {
1653
2138
  if (!defaultTempo) {
1654
- defaultTempo = { beatsPerMinute: 120, options: { beatLength: 48 /* Quarter */, dotted: false } };
2139
+ defaultTempo = { beatsPerMinute: 120, options: { beatLength: "4n" /* Quarter */, dotCount: 0 } };
1655
2140
  }
1656
2141
  return defaultTempo;
1657
2142
  }
1658
2143
  function getTempoString(tempo) {
1659
- return new RhythmProps(tempo.options.beatLength, tempo.options.dotted).toString() + "=" + tempo.beatsPerMinute;
2144
+ return RhythmProps.get(tempo.options.beatLength, tempo.options.dotCount).toString() + "=" + tempo.beatsPerMinute;
1660
2145
  }
1661
2146
  function alterTempoSpeed(tempo, speed) {
1662
2147
  return {
1663
2148
  beatsPerMinute: tempo.beatsPerMinute * speed,
1664
- options: { beatLength: tempo.options.beatLength, dotted: tempo.options.dotted }
2149
+ options: { beatLength: tempo.options.beatLength, dotCount: tempo.options.dotCount }
1665
2150
  };
1666
2151
  }
1667
2152
 
@@ -1680,11 +2165,10 @@ export {
1680
2165
  Handedness,
1681
2166
  Interval,
1682
2167
  KeySignature,
1683
- MaxNoteLength,
1684
- MinNoteLength,
1685
2168
  Mode,
1686
2169
  Note,
1687
2170
  NoteLength,
2171
+ NoteLengthProps,
1688
2172
  PitchNotation,
1689
2173
  PitchNotationList,
1690
2174
  RhythmProps,
@@ -1694,6 +2178,7 @@ export {
1694
2178
  SymbolSet,
1695
2179
  TimeSignature,
1696
2180
  TuningNameList,
2181
+ Tuplet,
1697
2182
  alterTempoSpeed,
1698
2183
  getDefaultKeySignature,
1699
2184
  getDefaultScale,
@@ -1711,6 +2196,7 @@ export {
1711
2196
  validateNoteLength,
1712
2197
  validatePitchNotation,
1713
2198
  validateScaleType,
1714
- validateTuningName
2199
+ validateTuningName,
2200
+ validateTupletRatio
1715
2201
  };
1716
2202
  //# sourceMappingURL=index.mjs.map