@musodojo/music-theory-data 23.0.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/esm/src/utils/accidentals.d.ts +9 -0
  2. package/esm/src/utils/accidentals.d.ts.map +1 -0
  3. package/esm/src/utils/accidentals.js +55 -0
  4. package/esm/src/utils/intervals.d.ts +46 -19
  5. package/esm/src/utils/intervals.d.ts.map +1 -1
  6. package/esm/src/utils/intervals.js +133 -36
  7. package/esm/src/utils/midi-note-sequences.js +3 -3
  8. package/esm/src/utils/midi.d.ts +4 -4
  9. package/esm/src/utils/midi.d.ts.map +1 -1
  10. package/esm/src/utils/midi.js +5 -5
  11. package/esm/src/utils/mod.d.ts +1 -0
  12. package/esm/src/utils/mod.d.ts.map +1 -1
  13. package/esm/src/utils/mod.js +1 -0
  14. package/esm/src/utils/note-names.d.ts +14 -0
  15. package/esm/src/utils/note-names.d.ts.map +1 -1
  16. package/esm/src/utils/note-names.js +26 -44
  17. package/esm/src/utils/qualities.d.ts +31 -0
  18. package/esm/src/utils/qualities.d.ts.map +1 -0
  19. package/esm/src/utils/qualities.js +53 -0
  20. package/esm/src/utils/rotate-array.d.ts +11 -1
  21. package/esm/src/utils/rotate-array.d.ts.map +1 -1
  22. package/esm/src/utils/rotate-array.js +17 -6
  23. package/esm/tests/interval-strings.test.d.ts.map +1 -0
  24. package/esm/tests/qualities.test.d.ts.map +1 -0
  25. package/package.json +1 -1
  26. package/script/src/utils/accidentals.d.ts +9 -0
  27. package/script/src/utils/accidentals.d.ts.map +1 -0
  28. package/script/src/utils/accidentals.js +59 -0
  29. package/script/src/utils/intervals.d.ts +46 -19
  30. package/script/src/utils/intervals.d.ts.map +1 -1
  31. package/script/src/utils/intervals.js +139 -37
  32. package/script/src/utils/midi-note-sequences.js +2 -2
  33. package/script/src/utils/midi.d.ts +4 -4
  34. package/script/src/utils/midi.d.ts.map +1 -1
  35. package/script/src/utils/midi.js +9 -9
  36. package/script/src/utils/mod.d.ts +1 -0
  37. package/script/src/utils/mod.d.ts.map +1 -1
  38. package/script/src/utils/mod.js +1 -0
  39. package/script/src/utils/note-names.d.ts +14 -0
  40. package/script/src/utils/note-names.d.ts.map +1 -1
  41. package/script/src/utils/note-names.js +28 -44
  42. package/script/src/utils/qualities.d.ts +31 -0
  43. package/script/src/utils/qualities.d.ts.map +1 -0
  44. package/script/src/utils/qualities.js +58 -0
  45. package/script/src/utils/rotate-array.d.ts +11 -1
  46. package/script/src/utils/rotate-array.d.ts.map +1 -1
  47. package/script/src/utils/rotate-array.js +18 -6
  48. package/script/tests/interval-strings.test.d.ts.map +1 -0
  49. package/script/tests/qualities.test.d.ts.map +1 -0
@@ -0,0 +1,9 @@
1
+ export declare const ACCIDENTAL_REGEX: RegExp;
2
+ /**
3
+ * Parses an accidental string containing ASCII or standard accidental characters
4
+ * and normalizes it into canonical accidental symbols ('♯', '♭', '𝄪', '𝄫').
5
+ * @param accidentalString The string containing only accidentals.
6
+ * @returns A canonical accidental string, or undefined if the string contained invalid characters.
7
+ */
8
+ export declare function normalizeAccidentalString(accidentalString: string): string | undefined;
9
+ //# sourceMappingURL=accidentals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accidentals.d.ts","sourceRoot":"","sources":["../../../src/src/utils/accidentals.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,QAA4B,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,gBAAgB,EAAE,MAAM,GACvB,MAAM,GAAG,SAAS,CAoDpB"}
@@ -0,0 +1,55 @@
1
+ export const ACCIDENTAL_REGEX = /([#♯xX𝄪]+)|([b♭𝄫]+)/gu;
2
+ /**
3
+ * Parses an accidental string containing ASCII or standard accidental characters
4
+ * and normalizes it into canonical accidental symbols ('♯', '♭', '𝄪', '𝄫').
5
+ * @param accidentalString The string containing only accidentals.
6
+ * @returns A canonical accidental string, or undefined if the string contained invalid characters.
7
+ */
8
+ export function normalizeAccidentalString(accidentalString) {
9
+ if (accidentalString.length === 0)
10
+ return "";
11
+ let validAccidentalLength = 0;
12
+ let alterInteger = 0;
13
+ for (const match of accidentalString.matchAll(ACCIDENTAL_REGEX)) {
14
+ const sharps = match[1];
15
+ if (sharps) {
16
+ for (const char of sharps) {
17
+ alterInteger += char.toLowerCase() === "x" || char === "𝄪" ? 2 : 1;
18
+ }
19
+ validAccidentalLength += sharps.length;
20
+ }
21
+ const flats = match[2];
22
+ if (flats) {
23
+ for (const char of flats) {
24
+ alterInteger -= char === "𝄫" ? 2 : 1;
25
+ }
26
+ validAccidentalLength += flats.length;
27
+ }
28
+ }
29
+ if (accidentalString.length > validAccidentalLength) {
30
+ return undefined;
31
+ }
32
+ let accidentalSymbols = "";
33
+ let currentAlteration = alterInteger;
34
+ while (currentAlteration > 0) {
35
+ if (currentAlteration >= 2) {
36
+ accidentalSymbols += "𝄪";
37
+ currentAlteration -= 2;
38
+ }
39
+ else {
40
+ accidentalSymbols += "♯";
41
+ currentAlteration -= 1;
42
+ }
43
+ }
44
+ while (currentAlteration < 0) {
45
+ if (currentAlteration <= -2) {
46
+ accidentalSymbols += "𝄫";
47
+ currentAlteration += 2;
48
+ }
49
+ else {
50
+ accidentalSymbols += "♭";
51
+ currentAlteration += 1;
52
+ }
53
+ }
54
+ return accidentalSymbols;
55
+ }
@@ -1,5 +1,4 @@
1
- import { type Interval, type IntervalQuality } from "../data/labels/note-labels.js";
2
- import type { NoteCollection } from "../types/note-collections";
1
+ import { type CompoundInterval, type Interval, type IntervalQuality, type SimpleInterval } from "../data/labels/note-labels.js";
3
2
  import { type NoteCollectionKey } from "../data/note-collections/mod.js";
4
3
  /**
5
4
  * Removes octave intervals (such as "8" or "♮8") from a given list of intervals.
@@ -10,6 +9,46 @@ import { type NoteCollectionKey } from "../data/note-collections/mod.js";
10
9
  * @returns A new array excluding any octave intervals.
11
10
  */
12
11
  export declare function filterOutOctaveIntervals(intervals: readonly Interval[]): Interval[];
12
+ /**
13
+ * Parses a string and returns a canonical `Interval` if the string is a valid interval.
14
+ * This handles ASCII accidentals like 'b' and '#' as well as qualities like 'M3', 'm3'.
15
+ * @param name The string to parse.
16
+ * @returns A canonical `Interval` (e.g., "♭3", "♯4") or `undefined` if invalid.
17
+ */
18
+ export declare function normalizeIntervalString(name: string): Interval | undefined;
19
+ /**
20
+ * Parses an array of strings and returns an array of canonical `Interval`s.
21
+ * Any invalid interval strings are filtered out from the result.
22
+ * @param names The array of strings to parse.
23
+ * @returns An array of canonical `Interval`s.
24
+ */
25
+ export declare function normalizeIntervalStringArray(names: readonly string[]): Interval[];
26
+ /**
27
+ * Parses a string and returns a canonical `SimpleInterval` if valid.
28
+ */
29
+ export declare function normalizeSimpleIntervalString(name: string): SimpleInterval | undefined;
30
+ /**
31
+ * Parses an array of strings and returns an array of canonical `SimpleInterval`s.
32
+ */
33
+ export declare function normalizeSimpleIntervalStringArray(names: readonly string[]): SimpleInterval[];
34
+ /**
35
+ * Parses a string and returns a canonical `CompoundInterval` if valid.
36
+ */
37
+ export declare function normalizeCompoundIntervalString(name: string): CompoundInterval | undefined;
38
+ /**
39
+ * Parses an array of strings and returns an array of canonical `CompoundInterval`s.
40
+ */
41
+ export declare function normalizeCompoundIntervalStringArray(names: readonly string[]): CompoundInterval[];
42
+ /**
43
+ * Removes any intervals that mathematically resolve to the root note (0 modulo 12),
44
+ * except for the unison ("1" or "♮1") itself if it is present.
45
+ * This effectively prevents octaves ("8", "15") from overwriting the explicit unison
46
+ * slot when mapping intervals into a strict 12-semitone chromatic scale.
47
+ *
48
+ * @param intervals The array of intervals to filter.
49
+ * @returns A new array excluding higher root-equivalent intervals.
50
+ */
51
+ export declare function filterOutRootLikeIntervals(intervals: readonly Interval[]): Interval[];
13
52
  /**
14
53
  * Sorts an array of intervals in ascending order based on their integer value.
15
54
  * This is a pure function and returns a new sorted array, leaving the original array unchanged.
@@ -37,10 +76,11 @@ export type TransformIntervalsOptions = {
37
76
  */
38
77
  shouldSort?: boolean;
39
78
  /**
40
- * A fixed number of steps to rotate the array left (positive) or right (negative).
41
- * Rotation pushes elements from the beginning of the array to the end (left) or vice versa.
79
+ * A fixed number of steps to rotate the array right (positive) or left (negative).
80
+ * Positive values loop elements at the end of the array to the front.
81
+ * Negative values loop elements at the front of the array to the end.
42
82
  */
43
- rotateLeft?: number;
83
+ rotateRight?: number;
44
84
  } & ({
45
85
  /**
46
86
  * When true, generates a 12-element array representing the chromatic scale (0-11).
@@ -79,24 +119,11 @@ export type TransformIntervalsOptions = {
79
119
  * @returns A new transformed array of intervals.
80
120
  */
81
121
  export declare function transformIntervals(intervals: readonly Interval[], options?: TransformIntervalsOptions): Interval[];
82
- /**
83
- * Extracts generic interval qualities (e.g., "P5", "m3" becomes "P", "m") from a list of intervals.
84
- *
85
- * @param intervals An array of specific intervals.
86
- * @returns An array of the corresponding interval qualities.
87
- */
88
- export declare function getQualitiesFromIntervals(intervals: readonly Interval[]): IntervalQuality[];
89
122
  /**
90
123
  * Retrieves the base intervals associated with a given set of interval qualities.
91
124
  *
92
125
  * @param qualities An array of generic interval qualities.
93
126
  * @returns An array of corresponding base intervals.
94
127
  */
95
- export declare function getIntervalsFromQualities(qualities: IntervalQuality[]): Interval[];
96
- /**
97
- * Extracts the interval qualities (e.g., "P1", "M2", "m3") from a NoteCollection.
98
- * @param collection The NoteCollection object.
99
- * @returns An array of IntervalQuality strings.
100
- */
101
- export declare function getQualitiesFromNoteCollection(collection: NoteCollection): IntervalQuality[];
128
+ export declare function getIntervalsFromQualities(qualities: readonly IntervalQuality[]): Interval[];
102
129
  //# sourceMappingURL=intervals.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"intervals.d.ts","sourceRoot":"","sources":["../../../src/src/utils/intervals.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,eAAe,EAMrB,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,iCAAiC,CAAC;AAIzC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAC7B,QAAQ,EAAE,CAEZ;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAOxE;AAED,4FAA4F;AAC5F,MAAM,MAAM,sBAAsB,GAC9B,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,GAClB,kBAAkB,CAAC;AAEvB,kFAAkF;AAClF,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,CACA;IACE;;;;OAIG;IACH,aAAa,EAAE,IAAI,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,GACD;IACE,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,gBAAgB,CAAC,EAAE,KAAK,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,CAAC;IACxB,oBAAoB,CAAC,EAAE,KAAK,CAAC;CAC9B,CACJ,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,GAAE,yBAA8B,GACtC,QAAQ,EAAE,CAiFZ;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAC7B,eAAe,EAAE,CAKnB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,eAAe,EAAE,GAC3B,QAAQ,EAAE,CAKZ;AAED;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,cAAc,GACzB,eAAe,EAAE,CAEnB"}
1
+ {"version":3,"file":"intervals.d.ts","sourceRoot":"","sources":["../../../src/src/utils/intervals.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAGrB,KAAK,QAAQ,EACb,KAAK,eAAe,EAGpB,KAAK,cAAc,EAGpB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,iCAAiC,CAAC;AAOzC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAC7B,QAAQ,EAAE,CAEZ;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAmC1E;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,QAAQ,EAAE,CAIZ;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,GACX,cAAc,GAAG,SAAS,CAU5B;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CAChD,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,cAAc,EAAE,CAIlB;AAED;;GAEG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,GACX,gBAAgB,GAAG,SAAS,CAU9B;AAED;;GAEG;AACH,wBAAgB,oCAAoC,CAClD,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,gBAAgB,EAAE,CAIpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAC7B,QAAQ,EAAE,CAuBZ;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAOxE;AAED,4FAA4F;AAC5F,MAAM,MAAM,sBAAsB,GAC9B,mBAAmB,GACnB,mBAAmB,GACnB,kBAAkB,GAClB,kBAAkB,CAAC;AAEvB,kFAAkF;AAClF,MAAM,MAAM,yBAAyB,GACjC;IACA;;;OAGG;IACH,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;IAChD;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GACC,CACE;IACA;;;;OAIG;IACH,aAAa,EAAE,IAAI,CAAC;IACpB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,iBAAiB,CAAC;IACrC;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC,GACC;IACA,aAAa,CAAC,EAAE,KAAK,CAAC;IACtB,gBAAgB,CAAC,EAAE,KAAK,CAAC;IACzB,eAAe,CAAC,EAAE,KAAK,CAAC;IACxB,oBAAoB,CAAC,EAAE,KAAK,CAAC;CAC9B,CACF,CAAC;AAEJ;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,GAAE,yBAA8B,GACtC,QAAQ,EAAE,CAwFZ;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,eAAe,EAAE,GACpC,QAAQ,EAAE,CAKZ"}
@@ -1,7 +1,9 @@
1
- import { compoundToSimpleIntervalMap, extensionToSimpleIntervalMap, intervalQualityToIntervalMap, intervalToIntegerMap, intervalToIntervalQualityMap, simpleToCompoundIntervalMap, simpleToExtensionIntervalMap, } from "../data/labels/note-labels.js";
1
+ import { compoundToSimpleIntervalMap, extensionToSimpleIntervalMap, intervalQualityToIntervalMap, intervalToIntegerMap, simpleToCompoundIntervalMap, simpleToExtensionIntervalMap, } from "../data/labels/note-labels.js";
2
2
  import { noteCollections, } from "../data/note-collections/mod.js";
3
- import { noteLabelCollections } from "../mod.js";
4
- import { rotateArrayLeft } from "./rotate-array.js";
3
+ import { noteLabelCollections } from "../data/labels/note-label-collections.js";
4
+ import { rotateArrayRight } from "./rotate-array.js";
5
+ import { normalizeAccidentalString } from "./accidentals.js";
6
+ const INTERVAL_NUMBER_REGEX = /\d+$/;
5
7
  /**
6
8
  * Removes octave intervals (such as "8" or "♮8") from a given list of intervals.
7
9
  * Highly useful for standardizing chord and scale definitions (scales conventionally include the octave,
@@ -13,6 +15,121 @@ import { rotateArrayLeft } from "./rotate-array.js";
13
15
  export function filterOutOctaveIntervals(intervals) {
14
16
  return intervals.filter((i) => i !== "8" && i !== "♮8");
15
17
  }
18
+ /**
19
+ * Parses a string and returns a canonical `Interval` if the string is a valid interval.
20
+ * This handles ASCII accidentals like 'b' and '#' as well as qualities like 'M3', 'm3'.
21
+ * @param name The string to parse.
22
+ * @returns A canonical `Interval` (e.g., "♭3", "♯4") or `undefined` if invalid.
23
+ */
24
+ export function normalizeIntervalString(name) {
25
+ if (typeof name !== "string" || name.length === 0) {
26
+ return undefined;
27
+ }
28
+ // 1. Check if it's an explicit interval quality like "M3" or "d5"
29
+ if (intervalQualityToIntervalMap.has(name)) {
30
+ return intervalQualityToIntervalMap.get(name);
31
+ }
32
+ // 2. Check if it's already a canonical interval like "♭3" or "3"
33
+ if (intervalToIntegerMap.has(name)) {
34
+ return name;
35
+ }
36
+ // 3. Try to parse ASCII accidentals before the interval number
37
+ const numberMatch = name.match(INTERVAL_NUMBER_REGEX);
38
+ if (!numberMatch) {
39
+ return undefined;
40
+ }
41
+ const intervalNumberStr = numberMatch[0];
42
+ const accidentalString = name.substring(0, name.length - intervalNumberStr.length);
43
+ const accidentalSymbols = normalizeAccidentalString(accidentalString);
44
+ if (accidentalSymbols === undefined) {
45
+ return undefined;
46
+ }
47
+ const result = (accidentalSymbols + intervalNumberStr);
48
+ return intervalToIntegerMap.has(result) ? result : undefined;
49
+ }
50
+ /**
51
+ * Parses an array of strings and returns an array of canonical `Interval`s.
52
+ * Any invalid interval strings are filtered out from the result.
53
+ * @param names The array of strings to parse.
54
+ * @returns An array of canonical `Interval`s.
55
+ */
56
+ export function normalizeIntervalStringArray(names) {
57
+ return names
58
+ .map((name) => normalizeIntervalString(name))
59
+ .filter((name) => name !== undefined);
60
+ }
61
+ /**
62
+ * Parses a string and returns a canonical `SimpleInterval` if valid.
63
+ */
64
+ export function normalizeSimpleIntervalString(name) {
65
+ const normalized = normalizeIntervalString(name);
66
+ if (normalized) {
67
+ // A simple interval has a number <= 8
68
+ const num = parseInt(normalized.match(INTERVAL_NUMBER_REGEX)[0], 10);
69
+ if (num <= 8) {
70
+ return normalized;
71
+ }
72
+ }
73
+ return undefined;
74
+ }
75
+ /**
76
+ * Parses an array of strings and returns an array of canonical `SimpleInterval`s.
77
+ */
78
+ export function normalizeSimpleIntervalStringArray(names) {
79
+ return names
80
+ .map((name) => normalizeSimpleIntervalString(name))
81
+ .filter((name) => name !== undefined);
82
+ }
83
+ /**
84
+ * Parses a string and returns a canonical `CompoundInterval` if valid.
85
+ */
86
+ export function normalizeCompoundIntervalString(name) {
87
+ const normalized = normalizeIntervalString(name);
88
+ if (normalized) {
89
+ // A compound interval has a number >= 9
90
+ const num = parseInt(normalized.match(INTERVAL_NUMBER_REGEX)[0], 10);
91
+ if (num >= 9) {
92
+ return normalized;
93
+ }
94
+ }
95
+ return undefined;
96
+ }
97
+ /**
98
+ * Parses an array of strings and returns an array of canonical `CompoundInterval`s.
99
+ */
100
+ export function normalizeCompoundIntervalStringArray(names) {
101
+ return names
102
+ .map((name) => normalizeCompoundIntervalString(name))
103
+ .filter((name) => name !== undefined);
104
+ }
105
+ /**
106
+ * Removes any intervals that mathematically resolve to the root note (0 modulo 12),
107
+ * except for the unison ("1" or "♮1") itself if it is present.
108
+ * This effectively prevents octaves ("8", "15") from overwriting the explicit unison
109
+ * slot when mapping intervals into a strict 12-semitone chromatic scale.
110
+ *
111
+ * @param intervals The array of intervals to filter.
112
+ * @returns A new array excluding higher root-equivalent intervals.
113
+ */
114
+ export function filterOutRootLikeIntervals(intervals) {
115
+ const hasUnison = intervals.some((i) => i === "1" || i === "♮1" || i === "𝄫2");
116
+ return intervals.filter((interval) => {
117
+ const semitones = intervalToIntegerMap.get(interval);
118
+ if (semitones === undefined)
119
+ return true; // keep it if invalid, though shouldn't happen
120
+ const isRootSlot = semitones % 12 === 0;
121
+ // If it falls on the root slot, we only keep it if it IS the unison
122
+ if (isRootSlot) {
123
+ if (hasUnison &&
124
+ interval !== "1" &&
125
+ interval !== "♮1" &&
126
+ interval !== "𝄫2") {
127
+ return false;
128
+ }
129
+ }
130
+ return true;
131
+ });
132
+ }
16
133
  /**
17
134
  * Sorts an array of intervals in ascending order based on their integer value.
18
135
  * This is a pure function and returns a new sorted array, leaving the original array unchanged.
@@ -37,7 +154,7 @@ export function sortIntervals(intervals) {
37
154
  * @returns A new transformed array of intervals.
38
155
  */
39
156
  export function transformIntervals(intervals, options = {}) {
40
- const { intervalTransformation, filterOutOctave = false, shouldSort = true, fillChromatic = false, mostSimilarScale, rotateLeft, } = options;
157
+ const { intervalTransformation, filterOutOctave = false, shouldSort = true, fillChromatic = false, mostSimilarScale, rotateRight, rotateToRootInteger0, rootNoteInteger, } = options;
41
158
  const intervalMap = (() => {
42
159
  switch (intervalTransformation) {
43
160
  case "simpleToExtension":
@@ -55,7 +172,6 @@ export function transformIntervals(intervals, options = {}) {
55
172
  const fundamentalIntervals = filterOutOctave
56
173
  ? filterOutOctaveIntervals(intervals)
57
174
  : intervals;
58
- const transformedIntervals = fundamentalIntervals.map((interval) => intervalMap.get(interval) ?? interval);
59
175
  if (fillChromatic) {
60
176
  const chromaticMap = [
61
177
  ...noteLabelCollections.intervalsFlat.labels,
@@ -64,50 +180,39 @@ export function transformIntervals(intervals, options = {}) {
64
180
  const collection = noteCollections[mostSimilarScale];
65
181
  if (collection && collection.intervals !== intervals) {
66
182
  collection.intervals.forEach((interval) => {
67
- const transformed = intervalMap.get(interval) ?? interval;
68
- const semitones = intervalToIntegerMap.get(transformed);
183
+ const semitones = intervalToIntegerMap.get(interval);
69
184
  if (semitones !== undefined) {
70
- chromaticMap[semitones % 12] = transformed;
185
+ chromaticMap[semitones % 12] = interval;
71
186
  }
72
187
  });
73
188
  }
74
189
  }
75
190
  // Now overlay the provided parsed intervals
76
- transformedIntervals.forEach((interval) => {
191
+ const filteredIntervalsForOverlay = filterOutRootLikeIntervals(fundamentalIntervals);
192
+ filteredIntervalsForOverlay.forEach((interval) => {
77
193
  const semitones = intervalToIntegerMap.get(interval);
78
194
  if (semitones !== undefined) {
79
195
  chromaticMap[semitones % 12] = interval;
80
196
  }
81
197
  });
82
- let result = chromaticMap;
83
- if (options.rotateToRootInteger0 && options.rootNoteInteger !== undefined) {
84
- result = rotateArrayLeft(result, -options.rootNoteInteger);
198
+ let result = chromaticMap.map((interval) => intervalMap.get(interval) ?? interval);
199
+ if (rotateToRootInteger0 && rootNoteInteger !== undefined) {
200
+ result = rotateArrayRight(result, rootNoteInteger);
85
201
  }
86
- if (rotateLeft !== undefined) {
87
- result = rotateArrayLeft(result, rotateLeft);
202
+ if (rotateRight !== undefined) {
203
+ result = rotateArrayRight(result, rotateRight);
88
204
  }
89
205
  return result;
90
206
  }
207
+ const transformedIntervals = fundamentalIntervals.map((interval) => intervalMap.get(interval) ?? interval);
91
208
  let result = shouldSort
92
209
  ? sortIntervals(transformedIntervals)
93
210
  : transformedIntervals;
94
- if (rotateLeft !== undefined) {
95
- result = rotateArrayLeft(result, rotateLeft);
211
+ if (rotateRight !== undefined) {
212
+ result = rotateArrayRight(result, rotateRight);
96
213
  }
97
214
  return result;
98
215
  }
99
- /**
100
- * Extracts generic interval qualities (e.g., "P5", "m3" becomes "P", "m") from a list of intervals.
101
- *
102
- * @param intervals An array of specific intervals.
103
- * @returns An array of the corresponding interval qualities.
104
- */
105
- export function getQualitiesFromIntervals(intervals) {
106
- return intervals.flatMap((interval) => {
107
- const quality = intervalToIntervalQualityMap.get(interval);
108
- return quality ? [quality] : [];
109
- });
110
- }
111
216
  /**
112
217
  * Retrieves the base intervals associated with a given set of interval qualities.
113
218
  *
@@ -120,11 +225,3 @@ export function getIntervalsFromQualities(qualities) {
120
225
  return interval ? [interval] : [];
121
226
  });
122
227
  }
123
- /**
124
- * Extracts the interval qualities (e.g., "P1", "M2", "m3") from a NoteCollection.
125
- * @param collection The NoteCollection object.
126
- * @returns An array of IntervalQuality strings.
127
- */
128
- export function getQualitiesFromNoteCollection(collection) {
129
- return getQualitiesFromIntervals(collection.intervals);
130
- }
@@ -1,4 +1,4 @@
1
- import { noteMidiAndIntervalToMidi } from "./midi.js";
1
+ import { getMidiFromNoteMidiAndInterval } from "./midi.js";
2
2
  /**
3
3
  * Generates a monotonic sequence of MIDI notes.
4
4
  * @param rootNoteMidi The root MIDI note number.
@@ -21,7 +21,7 @@ function getMonotonicMidiNoteSequence(rootNoteMidi, intervals, numNotes, startFr
21
21
  intervalIndex = (startFromIndex + i) % intervalsLength;
22
22
  octaveOffset = Math.floor((startFromIndex + i) / intervalsLength) * 12;
23
23
  const interval = intervals[intervalIndex];
24
- const note = noteMidiAndIntervalToMidi(rootNoteMidi, interval);
24
+ const note = getMidiFromNoteMidiAndInterval(rootNoteMidi, interval);
25
25
  if (note === undefined) {
26
26
  throw new Error(`Could not calculate MIDI note for interval ${interval} at index ${intervalIndex}`);
27
27
  }
@@ -44,7 +44,7 @@ function getMonotonicMidiNoteSequence(rootNoteMidi, intervals, numNotes, startFr
44
44
  Math.max(0, Math.ceil((i - startFromIndex) / intervalsLength)) * 12;
45
45
  const interval = intervals[intervalIndex];
46
46
  // Subtract octaveOffset from root before applying interval
47
- const note = noteMidiAndIntervalToMidi((rootNoteMidi - octaveOffset), interval);
47
+ const note = getMidiFromNoteMidiAndInterval((rootNoteMidi - octaveOffset), interval);
48
48
  if (note === undefined) {
49
49
  throw new Error(`Could not calculate MIDI note for interval ${interval} at index ${intervalIndex}`);
50
50
  }
@@ -9,7 +9,7 @@ import type { MidiNoteNumber } from "../types/midi";
9
9
  * @param interval The interval to apply.
10
10
  * @returns The computed MIDI note number, or `undefined` if the interval is invalid.
11
11
  */
12
- export declare function noteIntegerAndIntervalToMidi(noteInteger: number, noteOctaveNumber: number, interval: Interval): MidiNoteNumber | undefined;
12
+ export declare function getMidiFromNoteIntegerAndInterval(noteInteger: number, noteOctaveNumber: number, interval: Interval): MidiNoteNumber | undefined;
13
13
  /**
14
14
  * Calculates a MIDI note number based on a starting note name and an interval.
15
15
  *
@@ -18,7 +18,7 @@ export declare function noteIntegerAndIntervalToMidi(noteInteger: number, noteOc
18
18
  * @param interval The interval to apply.
19
19
  * @returns The computed MIDI note number, or `undefined` if the note name or interval is invalid.
20
20
  */
21
- export declare function noteNameAndIntervalToMidi(noteName: NoteName, noteOctaveNumber: number, interval: Interval): MidiNoteNumber | undefined;
21
+ export declare function getMidiFromNoteNameAndInterval(noteName: NoteName, noteOctaveNumber: number, interval: Interval): MidiNoteNumber | undefined;
22
22
  /**
23
23
  * Calculates a new MIDI note number by applying a musical interval to a starting MIDI note number.
24
24
  *
@@ -26,7 +26,7 @@ export declare function noteNameAndIntervalToMidi(noteName: NoteName, noteOctave
26
26
  * @param interval The interval to add.
27
27
  * @returns The new computed MIDI note number, or `undefined` if the interval is invalid.
28
28
  */
29
- export declare function noteMidiAndIntervalToMidi(noteMidi: MidiNoteNumber, interval: Interval): MidiNoteNumber | undefined;
29
+ export declare function getMidiFromNoteMidiAndInterval(noteMidi: MidiNoteNumber, interval: Interval): MidiNoteNumber | undefined;
30
30
  /**
31
31
  * Converts a note name and its specific octave into a standard MIDI note number.
32
32
  *
@@ -34,5 +34,5 @@ export declare function noteMidiAndIntervalToMidi(noteMidi: MidiNoteNumber, inte
34
34
  * @param noteOctaveNumber The scientific pitch octave number.
35
35
  * @returns The standard MIDI note number, or `undefined` if the note name is invalid.
36
36
  */
37
- export declare function noteNameToMidi(noteName: NoteName, noteOctaveNumber: number): MidiNoteNumber | undefined;
37
+ export declare function getMidiFromNoteName(noteName: NoteName, noteOctaveNumber: number): MidiNoteNumber | undefined;
38
38
  //# sourceMappingURL=midi.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"midi.d.ts","sourceRoot":"","sources":["../../../src/src/utils/midi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,QAAQ,EAEd,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,QAAQ,GACjB,cAAc,GAAG,SAAS,CAM5B;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,QAAQ,EAClB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,QAAQ,GACjB,cAAc,GAAG,SAAS,CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,QAAQ,GACjB,cAAc,GAAG,SAAS,CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,QAAQ,EAClB,gBAAgB,EAAE,MAAM,GACvB,cAAc,GAAG,SAAS,CAI5B"}
1
+ {"version":3,"file":"midi.d.ts","sourceRoot":"","sources":["../../../src/src/utils/midi.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,QAAQ,EAEd,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD;;;;;;;;GAQG;AACH,wBAAgB,iCAAiC,CAC/C,WAAW,EAAE,MAAM,EACnB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,QAAQ,GACjB,cAAc,GAAG,SAAS,CAM5B;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,QAAQ,EAClB,gBAAgB,EAAE,MAAM,EACxB,QAAQ,EAAE,QAAQ,GACjB,cAAc,GAAG,SAAS,CAQ5B;AAED;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,QAAQ,GACjB,cAAc,GAAG,SAAS,CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,gBAAgB,EAAE,MAAM,GACvB,cAAc,GAAG,SAAS,CAI5B"}
@@ -8,7 +8,7 @@ import { intervalToIntegerMap, noteNameToIntegerMap, } from "../data/labels/note
8
8
  * @param interval The interval to apply.
9
9
  * @returns The computed MIDI note number, or `undefined` if the interval is invalid.
10
10
  */
11
- export function noteIntegerAndIntervalToMidi(noteInteger, noteOctaveNumber, interval) {
11
+ export function getMidiFromNoteIntegerAndInterval(noteInteger, noteOctaveNumber, interval) {
12
12
  const intervalInteger = intervalToIntegerMap.get(interval);
13
13
  if (intervalInteger === undefined)
14
14
  return undefined;
@@ -24,11 +24,11 @@ export function noteIntegerAndIntervalToMidi(noteInteger, noteOctaveNumber, inte
24
24
  * @param interval The interval to apply.
25
25
  * @returns The computed MIDI note number, or `undefined` if the note name or interval is invalid.
26
26
  */
27
- export function noteNameAndIntervalToMidi(noteName, noteOctaveNumber, interval) {
27
+ export function getMidiFromNoteNameAndInterval(noteName, noteOctaveNumber, interval) {
28
28
  const noteInteger = noteNameToIntegerMap.get(noteName);
29
29
  if (noteInteger === undefined)
30
30
  return undefined;
31
- return noteIntegerAndIntervalToMidi(noteInteger, noteOctaveNumber, interval);
31
+ return getMidiFromNoteIntegerAndInterval(noteInteger, noteOctaveNumber, interval);
32
32
  }
33
33
  /**
34
34
  * Calculates a new MIDI note number by applying a musical interval to a starting MIDI note number.
@@ -37,7 +37,7 @@ export function noteNameAndIntervalToMidi(noteName, noteOctaveNumber, interval)
37
37
  * @param interval The interval to add.
38
38
  * @returns The new computed MIDI note number, or `undefined` if the interval is invalid.
39
39
  */
40
- export function noteMidiAndIntervalToMidi(noteMidi, interval) {
40
+ export function getMidiFromNoteMidiAndInterval(noteMidi, interval) {
41
41
  const intervalValue = intervalToIntegerMap.get(interval);
42
42
  if (intervalValue === undefined)
43
43
  return undefined;
@@ -50,7 +50,7 @@ export function noteMidiAndIntervalToMidi(noteMidi, interval) {
50
50
  * @param noteOctaveNumber The scientific pitch octave number.
51
51
  * @returns The standard MIDI note number, or `undefined` if the note name is invalid.
52
52
  */
53
- export function noteNameToMidi(noteName, noteOctaveNumber) {
53
+ export function getMidiFromNoteName(noteName, noteOctaveNumber) {
54
54
  const noteValue = noteNameToIntegerMap.get(noteName);
55
55
  if (noteValue === undefined)
56
56
  return undefined;
@@ -6,4 +6,5 @@ export * from "./midi.js";
6
6
  export * from "./note-collections.js";
7
7
  export * from "./note-names.js";
8
8
  export * from "./rotate-array.js";
9
+ export * from "./qualities.js";
9
10
  //# sourceMappingURL=mod.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../../src/src/utils/mod.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,WAAW,CAAC;AAC1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC"}
1
+ {"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../../src/src/utils/mod.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,0BAA0B,CAAC;AACzC,cAAc,WAAW,CAAC;AAC1B,cAAc,uBAAuB,CAAC;AACtC,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC"}
@@ -6,3 +6,4 @@ export * from "./midi.js";
6
6
  export * from "./note-collections.js";
7
7
  export * from "./note-names.js";
8
8
  export * from "./rotate-array.js";
9
+ export * from "./qualities.js";
@@ -9,6 +9,13 @@ import { type TransformIntervalsOptions } from "./intervals.js";
9
9
  * @returns A canonical `NoteName` (e.g., "B♭", "C𝄪") or `undefined` if the input is not a valid note.
10
10
  */
11
11
  export declare function normalizeNoteNameString(name: string): NoteName | undefined;
12
+ /**
13
+ * Parses an array of strings and returns an array of canonical `NoteName`s.
14
+ * Any invalid note name strings are filtered out from the result.
15
+ * @param names The array of strings to parse.
16
+ * @returns An array of canonical `NoteName`s.
17
+ */
18
+ export declare function normalizeNoteNameStringArray(names: readonly string[]): NoteName[];
12
19
  /**
13
20
  * Parses a string and returns a canonical `RootNote` if the string is a valid root note.
14
21
  * This is useful for handling user input that may use ASCII characters for accidentals.
@@ -17,6 +24,13 @@ export declare function normalizeNoteNameString(name: string): NoteName | undefi
17
24
  * @returns A canonical `RootNote` or `undefined` if the input is not a valid root note.
18
25
  */
19
26
  export declare function normalizeRootNoteString(name: string): RootNote | undefined;
27
+ /**
28
+ * Parses an array of strings and returns an array of canonical `RootNote`s.
29
+ * Any invalid root note strings are filtered out from the result.
30
+ * @param names The array of strings to parse.
31
+ * @returns An array of canonical `RootNote`s.
32
+ */
33
+ export declare function normalizeRootNoteStringArray(names: readonly string[]): RootNote[];
20
34
  /**
21
35
  * Gets the integer representation (0-11, where C=0) of a given note name string.
22
36
  * @param noteName The note name string to evaluate.
@@ -1 +1 @@
1
- {"version":3,"file":"note-names.d.ts","sourceRoot":"","sources":["../../../src/src/utils/note-names.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAIb,KAAK,QAAQ,EAGb,KAAK,QAAQ,EACb,KAAK,eAAe,EAErB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,gBAAgB,CAAC;AAQxB;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAsE1E;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAQ1E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GACf,eAAe,GAAG,SAAS,CAI7B;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,GAAE,yBAA8B,GACtC,QAAQ,EAAE,CAgCZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAE,iBAAiB,EACpC,OAAO,GAAE,IAAI,CAAC,yBAAyB,EAAE,kBAAkB,CAAM,GAChE,QAAQ,EAAE,CAuBZ"}
1
+ {"version":3,"file":"note-names.d.ts","sourceRoot":"","sources":["../../../src/src/utils/note-names.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAIb,KAAK,QAAQ,EAGb,KAAK,QAAQ,EACb,KAAK,eAAe,EAErB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAEL,KAAK,yBAAyB,EAC/B,MAAM,gBAAgB,CAAC;AAQxB;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CA2B1E;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,QAAQ,EAAE,CAIZ;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAQ1E;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,QAAQ,EAAE,CAIZ;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,GACf,eAAe,GAAG,SAAS,CAI7B;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,OAAO,GAAE,yBAA8B,GACtC,QAAQ,EAAE,CA+BZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,QAAQ,EAClB,iBAAiB,EAAE,iBAAiB,EACpC,OAAO,GAAE,IAAI,CAAC,yBAAyB,EAAE,kBAAkB,CAAM,GAChE,QAAQ,EAAE,CAqBZ"}