@tonaljs/scale 4.10.0 → 4.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/dist/index.d.ts +10 -5
- package/dist/index.js +39 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +43 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -53,6 +53,33 @@ Scale.get("c5 pentatonic");
|
|
|
53
53
|
// }
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
### `Scale.detect(notes: string[], options: { tonic?: string, match?: "fit" | "exact" }) => string[]`
|
|
57
|
+
|
|
58
|
+
Find all scales that first a collection of notes with a given tonic:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
Scale.detect(["C", "D", "E", "F", "G", "A", "B"]);
|
|
62
|
+
// => ["C major", "C bebop", "C bebop major",
|
|
63
|
+
// "C ichikosucho", "C chromatic"];
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can pass an optional tonic (otherwise first note will be used):
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
Scale.detect(["C", "D", "E", "F", "G", "A", "B"], { tonic: "A" });
|
|
70
|
+
// => [ 'A aeolian', 'A minor bebop', 'A chromatic' ]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
You can ask just the exact match:
|
|
74
|
+
|
|
75
|
+
````js
|
|
76
|
+
Scale.detect(["D", "E", "F#", "A", "B"], { match: "exact" });
|
|
77
|
+
// => ["D major pentatonic"]
|
|
78
|
+
Scale.detect(["D", "E", "F#", "A", "B"], { match: "exact", tonic: "B" });
|
|
79
|
+
// => ["B major pentatonic"]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
|
|
56
83
|
### `Scale.scaleChords(scale: string) => string[]`
|
|
57
84
|
|
|
58
85
|
Get all chords that fits a given scale:
|
|
@@ -60,7 +87,7 @@ Get all chords that fits a given scale:
|
|
|
60
87
|
```js
|
|
61
88
|
Scale.scaleChords("pentatonic");
|
|
62
89
|
// => ["5", "64", "M", "M6", "Madd9", "Msus2"]
|
|
63
|
-
|
|
90
|
+
````
|
|
64
91
|
|
|
65
92
|
### `Scale.extended(scale: string) => string[]`
|
|
66
93
|
|
|
@@ -133,7 +160,7 @@ Because it returns a function, it's handy to be used with `map` (and similar fun
|
|
|
133
160
|
|
|
134
161
|
Notice that it uses octaves if the scale tonic has an octave or pitch classes (_octaveless_ notes) otherwise.
|
|
135
162
|
|
|
136
|
-
See `Chord.degrees`
|
|
163
|
+
See [`Chord.degrees`](https://github.com/tonaljs/tonal/tree/main/packages/chord#chorddegreeschordname-string--degree-number--string)
|
|
137
164
|
|
|
138
165
|
See https://en.wikipedia.org/wiki/Degree_(music)
|
|
139
166
|
|
package/dist/index.d.ts
CHANGED
|
@@ -35,6 +35,10 @@ declare const names: typeof names$1;
|
|
|
35
35
|
*/
|
|
36
36
|
declare function get(src: ScaleName | ScaleNameTokens): Scale;
|
|
37
37
|
declare const scale: (this: unknown, ...args: unknown[]) => Scale;
|
|
38
|
+
declare function detect(notes: string[], options?: {
|
|
39
|
+
tonic?: string;
|
|
40
|
+
match?: "exact" | "fit";
|
|
41
|
+
}): string[];
|
|
38
42
|
/**
|
|
39
43
|
* Get all chords that fits a given scale
|
|
40
44
|
*
|
|
@@ -107,17 +111,18 @@ declare function rangeOf(scale: string | string[]): (fromNote: string, toNote: s
|
|
|
107
111
|
*/
|
|
108
112
|
declare function degrees(scaleName: string | ScaleNameTokens): (degree: number) => string;
|
|
109
113
|
declare const _default: {
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
degrees: typeof degrees;
|
|
115
|
+
detect: typeof detect;
|
|
112
116
|
extended: typeof extended;
|
|
117
|
+
get: typeof get;
|
|
113
118
|
modeNames: typeof modeNames;
|
|
119
|
+
names: typeof names$1;
|
|
120
|
+
rangeOf: typeof rangeOf;
|
|
114
121
|
reduced: typeof reduced;
|
|
115
122
|
scaleChords: typeof scaleChords;
|
|
116
123
|
scaleNotes: typeof scaleNotes;
|
|
117
124
|
tokenize: typeof tokenize;
|
|
118
|
-
rangeOf: typeof rangeOf;
|
|
119
|
-
degrees: typeof degrees;
|
|
120
125
|
scale: (this: unknown, ...args: unknown[]) => Scale;
|
|
121
126
|
};
|
|
122
127
|
|
|
123
|
-
export { Scale, _default as default, degrees, extended, get, modeNames, names, rangeOf, reduced, scale, scaleChords, scaleNotes, tokenize };
|
|
128
|
+
export { Scale, _default as default, degrees, detect, extended, get, modeNames, names, rangeOf, reduced, scale, scaleChords, scaleNotes, tokenize };
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target,
|
|
7
|
-
for (var name in
|
|
8
|
-
__defProp(target, name, { get:
|
|
6
|
+
var __export = (target, all2) => {
|
|
7
|
+
for (var name in all2)
|
|
8
|
+
__defProp(target, name, { get: all2[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
var __copyProps = (to, from, except, desc) => {
|
|
11
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -22,6 +22,7 @@ var scale_exports = {};
|
|
|
22
22
|
__export(scale_exports, {
|
|
23
23
|
default: () => scale_default,
|
|
24
24
|
degrees: () => degrees,
|
|
25
|
+
detect: () => detect,
|
|
25
26
|
extended: () => extended,
|
|
26
27
|
get: () => get,
|
|
27
28
|
modeNames: () => modeNames,
|
|
@@ -79,14 +80,37 @@ function get(src) {
|
|
|
79
80
|
return { ...st, name, type, tonic, notes };
|
|
80
81
|
}
|
|
81
82
|
var scale = (0, import_core.deprecate)("Scale.scale", "Scale.get", get);
|
|
83
|
+
function detect(notes, options = {}) {
|
|
84
|
+
const notesChroma = (0, import_pcset.chroma)(notes);
|
|
85
|
+
const tonic = (0, import_core.note)(options.tonic ?? notes[0] ?? "");
|
|
86
|
+
const tonicChroma = tonic.chroma;
|
|
87
|
+
if (tonicChroma === void 0) {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const pitchClasses = notesChroma.split("");
|
|
91
|
+
pitchClasses[tonicChroma] = "1";
|
|
92
|
+
const scaleChroma = (0, import_collection.rotate)(tonicChroma, pitchClasses).join("");
|
|
93
|
+
const match = (0, import_scale_type.all)().find((scaleType) => scaleType.chroma === scaleChroma);
|
|
94
|
+
const results = [];
|
|
95
|
+
if (match) {
|
|
96
|
+
results.push(tonic.name + " " + match.name);
|
|
97
|
+
}
|
|
98
|
+
if (options.match === "exact") {
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
extended(scaleChroma).forEach((scaleName) => {
|
|
102
|
+
results.push(tonic.name + " " + scaleName);
|
|
103
|
+
});
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
82
106
|
function scaleChords(name) {
|
|
83
107
|
const s = get(name);
|
|
84
108
|
const inScale = (0, import_pcset.isSubsetOf)(s.chroma);
|
|
85
109
|
return (0, import_chord_type.all)().filter((chord) => inScale(chord.chroma)).map((chord) => chord.aliases[0]);
|
|
86
110
|
}
|
|
87
111
|
function extended(name) {
|
|
88
|
-
const
|
|
89
|
-
const isSuperset = (0, import_pcset.isSupersetOf)(
|
|
112
|
+
const chroma2 = (0, import_pcset.isChroma)(name) ? name : get(name).chroma;
|
|
113
|
+
const isSuperset = (0, import_pcset.isSupersetOf)(chroma2);
|
|
90
114
|
return (0, import_scale_type.all)().filter((scale2) => isSuperset(scale2.chroma)).map((scale2) => scale2.name);
|
|
91
115
|
}
|
|
92
116
|
function reduced(name) {
|
|
@@ -105,8 +129,8 @@ function modeNames(name) {
|
|
|
105
129
|
return [];
|
|
106
130
|
}
|
|
107
131
|
const tonics = s.tonic ? s.notes : s.intervals;
|
|
108
|
-
return (0, import_pcset.modes)(s.chroma).map((
|
|
109
|
-
const modeName = get(
|
|
132
|
+
return (0, import_pcset.modes)(s.chroma).map((chroma2, i) => {
|
|
133
|
+
const modeName = get(chroma2).name;
|
|
110
134
|
return modeName ? [tonics[i], modeName] : ["", ""];
|
|
111
135
|
}).filter((x) => x[0]);
|
|
112
136
|
}
|
|
@@ -118,8 +142,8 @@ function getNoteNameOf(scale2) {
|
|
|
118
142
|
const height = currNote.height;
|
|
119
143
|
if (height === void 0)
|
|
120
144
|
return void 0;
|
|
121
|
-
const
|
|
122
|
-
const position = chromas.indexOf(
|
|
145
|
+
const chroma2 = height % 12;
|
|
146
|
+
const position = chromas.indexOf(chroma2);
|
|
123
147
|
if (position === -1)
|
|
124
148
|
return void 0;
|
|
125
149
|
return (0, import_note.enharmonic)(currNote.name, names2[position]);
|
|
@@ -140,21 +164,23 @@ function degrees(scaleName) {
|
|
|
140
164
|
return (0, import_core.transposeIntervalSetByDegree)(scale2.intervals, scale2.tonic ?? "");
|
|
141
165
|
}
|
|
142
166
|
var scale_default = {
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
degrees,
|
|
168
|
+
detect,
|
|
145
169
|
extended,
|
|
170
|
+
get,
|
|
146
171
|
modeNames,
|
|
172
|
+
names,
|
|
173
|
+
rangeOf,
|
|
147
174
|
reduced,
|
|
148
175
|
scaleChords,
|
|
149
176
|
scaleNotes,
|
|
150
177
|
tokenize,
|
|
151
|
-
rangeOf,
|
|
152
|
-
degrees,
|
|
153
178
|
scale
|
|
154
179
|
};
|
|
155
180
|
// Annotate the CommonJS export names for ESM import in node:
|
|
156
181
|
0 && (module.exports = {
|
|
157
182
|
degrees,
|
|
183
|
+
detect,
|
|
158
184
|
extended,
|
|
159
185
|
get,
|
|
160
186
|
modeNames,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["/**\n * References:\n * - https://www.researchgate.net/publication/327567188_An_Algorithm_for_Spelling_the_Pitches_of_Any_Musical_Scale\n * @module scale\n */\nimport { all as chordTypes } from \"@tonaljs/chord-type\";\nimport { range as nums, rotate } from \"@tonaljs/collection\";\nimport {\n deprecate,\n note,\n NoteName,\n transpose,\n transposeIntervalSetByDegree,\n} from \"@tonaljs/core\";\nimport { enharmonic, fromMidi, sortedUniqNames } from \"@tonaljs/note\";\nimport { isSubsetOf, isSupersetOf, modes } from \"@tonaljs/pcset\";\nimport {\n all as scaleTypes,\n get as getScaleType,\n names as scaleTypeNames,\n ScaleType,\n} from \"@tonaljs/scale-type\";\n\ntype ScaleName = string;\ntype ScaleNameTokens = [string, string]; // [TONIC, SCALE TYPE]\n\nexport interface Scale extends ScaleType {\n tonic: string | null;\n type: string;\n notes: NoteName[];\n}\n\nconst NoScale: Scale = {\n empty: true,\n name: \"\",\n type: \"\",\n tonic: null,\n setNum: NaN,\n chroma: \"\",\n normalized: \"\",\n aliases: [],\n notes: [],\n intervals: [],\n};\n\n/**\n * Given a string with a scale name and (optionally) a tonic, split\n * that components.\n *\n * It retuns an array with the form [ name, tonic ] where tonic can be a\n * note name or null and name can be any arbitrary string\n * (this function doesn\"t check if that scale name exists)\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array} an array [tonic, name]\n * @example\n * tokenize(\"C mixolydean\") // => [\"C\", \"mixolydean\"]\n * tokenize(\"anything is valid\") // => [\"\", \"anything is valid\"]\n * tokenize() // => [\"\", \"\"]\n */\nexport function tokenize(name: ScaleName): ScaleNameTokens {\n if (typeof name !== \"string\") {\n return [\"\", \"\"];\n }\n const i = name.indexOf(\" \");\n const tonic = note(name.substring(0, i));\n if (tonic.empty) {\n const n = note(name);\n return n.empty ? [\"\", name] : [n.name, \"\"];\n }\n\n const type = name.substring(tonic.name.length + 1);\n return [tonic.name, type.length ? type : \"\"];\n}\n\n/**\n * Get all scale names\n * @function\n */\nexport const names = scaleTypeNames;\n\n/**\n * Get a Scale from a scale name.\n */\nexport function get(src: ScaleName | ScaleNameTokens): Scale {\n const tokens = Array.isArray(src) ? src : tokenize(src);\n const tonic = note(tokens[0]).name;\n const st = getScaleType(tokens[1]);\n if (st.empty) {\n return NoScale;\n }\n\n const type = st.name;\n const notes: string[] = tonic\n ? st.intervals.map((i) => transpose(tonic, i))\n : [];\n\n const name = tonic ? tonic + \" \" + type : type;\n\n return { ...st, name, type, tonic, notes };\n}\n\nexport const scale = deprecate(\"Scale.scale\", \"Scale.get\", get);\n\n/**\n * Get all chords that fits a given scale\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array<string>} - the chord names\n *\n * @example\n * scaleChords(\"pentatonic\") // => [\"5\", \"64\", \"M\", \"M6\", \"Madd9\", \"Msus2\"]\n */\nexport function scaleChords(name: string): string[] {\n const s = get(name);\n const inScale = isSubsetOf(s.chroma);\n return chordTypes()\n .filter((chord) => inScale(chord.chroma))\n .map((chord) => chord.aliases[0]);\n}\n/**\n * Get all scales names that are a superset of the given one\n * (has the same notes and at least one more)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n * @example\n * extended(\"major\") // => [\"bebop\", \"bebop dominant\", \"bebop major\", \"chromatic\", \"ichikosucho\"]\n */\nexport function extended(name: string): string[] {\n const s = get(name);\n const isSuperset = isSupersetOf(s.chroma);\n return scaleTypes()\n .filter((scale) => isSuperset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Find all scales names that are a subset of the given one\n * (has less notes but all from the given scale)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n *\n * @example\n * reduced(\"major\") // => [\"ionian pentatonic\", \"major pentatonic\", \"ritusen\"]\n */\nexport function reduced(name: string): string[] {\n const isSubset = isSubsetOf(get(name).chroma);\n return scaleTypes()\n .filter((scale) => isSubset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Given an array of notes, return the scale: a pitch class set starting from\n * the first note of the array\n *\n * @function\n * @param {string[]} notes\n * @return {string[]} pitch classes with same tonic\n * @example\n * scaleNotes(['C4', 'c3', 'C5', 'C4', 'c4']) // => [\"C\"]\n * scaleNotes(['D4', 'c#5', 'A5', 'F#6']) // => [\"D\", \"F#\", \"A\", \"C#\"]\n */\nexport function scaleNotes(notes: NoteName[]) {\n const pcset: string[] = notes.map((n) => note(n).pc).filter((x) => x);\n const tonic = pcset[0];\n const scale = sortedUniqNames(pcset);\n return rotate(scale.indexOf(tonic), scale);\n}\n\ntype ScaleMode = [string, string];\n/**\n * Find mode names of a scale\n *\n * @function\n * @param {string} name - scale name\n * @example\n * modeNames(\"C pentatonic\") // => [\n * [\"C\", \"major pentatonic\"],\n * [\"D\", \"egyptian\"],\n * [\"E\", \"malkos raga\"],\n * [\"G\", \"ritusen\"],\n * [\"A\", \"minor pentatonic\"]\n * ]\n */\nexport function modeNames(name: string): ScaleMode[] {\n const s = get(name);\n if (s.empty) {\n return [];\n }\n\n const tonics = s.tonic ? s.notes : s.intervals;\n return modes(s.chroma)\n .map((chroma: string, i: number): ScaleMode => {\n const modeName = get(chroma).name;\n return modeName ? [tonics[i], modeName] : [\"\", \"\"];\n })\n .filter((x) => x[0]);\n}\n\nfunction getNoteNameOf(scale: string | string[]) {\n const names = Array.isArray(scale) ? scaleNotes(scale) : get(scale).notes;\n const chromas = names.map((name) => note(name).chroma);\n\n return (noteOrMidi: string | number): string | undefined => {\n const currNote =\n typeof noteOrMidi === \"number\"\n ? note(fromMidi(noteOrMidi))\n : note(noteOrMidi);\n const height = currNote.height;\n\n if (height === undefined) return undefined;\n const chroma = height % 12;\n const position = chromas.indexOf(chroma);\n if (position === -1) return undefined;\n return enharmonic(currNote.name, names[position]);\n };\n}\n\nexport function rangeOf(scale: string | string[]) {\n const getName = getNoteNameOf(scale);\n return (fromNote: string, toNote: string) => {\n const from = note(fromNote).height;\n const to = note(toNote).height;\n if (from === undefined || to === undefined) return [];\n\n return nums(from, to)\n .map(getName)\n .filter((x) => x);\n };\n}\n\n/**\n * Returns a function to get a note name from the scale degree.\n *\n * @example\n * [1, 2, 3].map(Scale.degrees(\"C major\")) => [\"C\", \"D\", \"E\"]\n * [1, 2, 3].map(Scale.degrees(\"C4 major\")) => [\"C4\", \"D4\", \"E4\"]\n */\nexport function degrees(scaleName: string | ScaleNameTokens) {\n const scale = get(scaleName);\n return transposeIntervalSetByDegree(scale.intervals, scale.tonic ?? \"\");\n}\n\nexport default {\n get,\n names,\n extended,\n modeNames,\n reduced,\n scaleChords,\n scaleNotes,\n tokenize,\n rangeOf,\n degrees,\n // deprecated\n scale,\n};\n"],"mappings":"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","names":["scaleTypeNames","getScaleType","chordTypes","scaleTypes","scale","names","nums"]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["/**\n * References:\n * - https://www.researchgate.net/publication/327567188_An_Algorithm_for_Spelling_the_Pitches_of_Any_Musical_Scale\n * @module scale\n */\nimport { all as chordTypes } from \"@tonaljs/chord-type\";\nimport { range as nums, rotate } from \"@tonaljs/collection\";\nimport {\n deprecate,\n note,\n NoteName,\n transpose,\n transposeIntervalSetByDegree,\n} from \"@tonaljs/core\";\nimport { enharmonic, fromMidi, sortedUniqNames } from \"@tonaljs/note\";\nimport {\n chroma,\n isChroma,\n isSubsetOf,\n isSupersetOf,\n modes,\n} from \"@tonaljs/pcset\";\nimport {\n all,\n all as scaleTypes,\n get as getScaleType,\n names as scaleTypeNames,\n ScaleType,\n} from \"@tonaljs/scale-type\";\n\ntype ScaleName = string;\ntype ScaleNameTokens = [string, string]; // [TONIC, SCALE TYPE]\n\nexport interface Scale extends ScaleType {\n tonic: string | null;\n type: string;\n notes: NoteName[];\n}\n\nconst NoScale: Scale = {\n empty: true,\n name: \"\",\n type: \"\",\n tonic: null,\n setNum: NaN,\n chroma: \"\",\n normalized: \"\",\n aliases: [],\n notes: [],\n intervals: [],\n};\n\n/**\n * Given a string with a scale name and (optionally) a tonic, split\n * that components.\n *\n * It retuns an array with the form [ name, tonic ] where tonic can be a\n * note name or null and name can be any arbitrary string\n * (this function doesn\"t check if that scale name exists)\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array} an array [tonic, name]\n * @example\n * tokenize(\"C mixolydean\") // => [\"C\", \"mixolydean\"]\n * tokenize(\"anything is valid\") // => [\"\", \"anything is valid\"]\n * tokenize() // => [\"\", \"\"]\n */\nexport function tokenize(name: ScaleName): ScaleNameTokens {\n if (typeof name !== \"string\") {\n return [\"\", \"\"];\n }\n const i = name.indexOf(\" \");\n const tonic = note(name.substring(0, i));\n if (tonic.empty) {\n const n = note(name);\n return n.empty ? [\"\", name] : [n.name, \"\"];\n }\n\n const type = name.substring(tonic.name.length + 1);\n return [tonic.name, type.length ? type : \"\"];\n}\n\n/**\n * Get all scale names\n * @function\n */\nexport const names = scaleTypeNames;\n\n/**\n * Get a Scale from a scale name.\n */\nexport function get(src: ScaleName | ScaleNameTokens): Scale {\n const tokens = Array.isArray(src) ? src : tokenize(src);\n const tonic = note(tokens[0]).name;\n const st = getScaleType(tokens[1]);\n if (st.empty) {\n return NoScale;\n }\n\n const type = st.name;\n const notes: string[] = tonic\n ? st.intervals.map((i) => transpose(tonic, i))\n : [];\n\n const name = tonic ? tonic + \" \" + type : type;\n\n return { ...st, name, type, tonic, notes };\n}\n\nexport const scale = deprecate(\"Scale.scale\", \"Scale.get\", get);\n\nexport function detect(\n notes: string[],\n options: { tonic?: string; match?: \"exact\" | \"fit\" } = {}\n): string[] {\n const notesChroma = chroma(notes);\n const tonic = note(options.tonic ?? notes[0] ?? \"\");\n const tonicChroma = tonic.chroma;\n if (tonicChroma === undefined) {\n return [];\n }\n\n const pitchClasses = notesChroma.split(\"\");\n pitchClasses[tonicChroma] = \"1\";\n const scaleChroma = rotate(tonicChroma, pitchClasses).join(\"\");\n const match = all().find((scaleType) => scaleType.chroma === scaleChroma);\n\n const results: string[] = [];\n if (match) {\n results.push(tonic.name + \" \" + match.name);\n }\n if (options.match === \"exact\") {\n return results;\n }\n\n extended(scaleChroma).forEach((scaleName) => {\n results.push(tonic.name + \" \" + scaleName);\n });\n\n return results;\n}\n\n/**\n * Get all chords that fits a given scale\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array<string>} - the chord names\n *\n * @example\n * scaleChords(\"pentatonic\") // => [\"5\", \"64\", \"M\", \"M6\", \"Madd9\", \"Msus2\"]\n */\nexport function scaleChords(name: string): string[] {\n const s = get(name);\n const inScale = isSubsetOf(s.chroma);\n return chordTypes()\n .filter((chord) => inScale(chord.chroma))\n .map((chord) => chord.aliases[0]);\n}\n/**\n * Get all scales names that are a superset of the given one\n * (has the same notes and at least one more)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n * @example\n * extended(\"major\") // => [\"bebop\", \"bebop dominant\", \"bebop major\", \"chromatic\", \"ichikosucho\"]\n */\nexport function extended(name: string): string[] {\n const chroma = isChroma(name) ? name : get(name).chroma;\n const isSuperset = isSupersetOf(chroma);\n return scaleTypes()\n .filter((scale) => isSuperset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Find all scales names that are a subset of the given one\n * (has less notes but all from the given scale)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n *\n * @example\n * reduced(\"major\") // => [\"ionian pentatonic\", \"major pentatonic\", \"ritusen\"]\n */\nexport function reduced(name: string): string[] {\n const isSubset = isSubsetOf(get(name).chroma);\n return scaleTypes()\n .filter((scale) => isSubset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Given an array of notes, return the scale: a pitch class set starting from\n * the first note of the array\n *\n * @function\n * @param {string[]} notes\n * @return {string[]} pitch classes with same tonic\n * @example\n * scaleNotes(['C4', 'c3', 'C5', 'C4', 'c4']) // => [\"C\"]\n * scaleNotes(['D4', 'c#5', 'A5', 'F#6']) // => [\"D\", \"F#\", \"A\", \"C#\"]\n */\nexport function scaleNotes(notes: NoteName[]) {\n const pcset: string[] = notes.map((n) => note(n).pc).filter((x) => x);\n const tonic = pcset[0];\n const scale = sortedUniqNames(pcset);\n return rotate(scale.indexOf(tonic), scale);\n}\n\ntype ScaleMode = [string, string];\n/**\n * Find mode names of a scale\n *\n * @function\n * @param {string} name - scale name\n * @example\n * modeNames(\"C pentatonic\") // => [\n * [\"C\", \"major pentatonic\"],\n * [\"D\", \"egyptian\"],\n * [\"E\", \"malkos raga\"],\n * [\"G\", \"ritusen\"],\n * [\"A\", \"minor pentatonic\"]\n * ]\n */\nexport function modeNames(name: string): ScaleMode[] {\n const s = get(name);\n if (s.empty) {\n return [];\n }\n\n const tonics = s.tonic ? s.notes : s.intervals;\n return modes(s.chroma)\n .map((chroma: string, i: number): ScaleMode => {\n const modeName = get(chroma).name;\n return modeName ? [tonics[i], modeName] : [\"\", \"\"];\n })\n .filter((x) => x[0]);\n}\n\nfunction getNoteNameOf(scale: string | string[]) {\n const names = Array.isArray(scale) ? scaleNotes(scale) : get(scale).notes;\n const chromas = names.map((name) => note(name).chroma);\n\n return (noteOrMidi: string | number): string | undefined => {\n const currNote =\n typeof noteOrMidi === \"number\"\n ? note(fromMidi(noteOrMidi))\n : note(noteOrMidi);\n const height = currNote.height;\n\n if (height === undefined) return undefined;\n const chroma = height % 12;\n const position = chromas.indexOf(chroma);\n if (position === -1) return undefined;\n return enharmonic(currNote.name, names[position]);\n };\n}\n\nexport function rangeOf(scale: string | string[]) {\n const getName = getNoteNameOf(scale);\n return (fromNote: string, toNote: string) => {\n const from = note(fromNote).height;\n const to = note(toNote).height;\n if (from === undefined || to === undefined) return [];\n\n return nums(from, to)\n .map(getName)\n .filter((x) => x);\n };\n}\n\n/**\n * Returns a function to get a note name from the scale degree.\n *\n * @example\n * [1, 2, 3].map(Scale.degrees(\"C major\")) => [\"C\", \"D\", \"E\"]\n * [1, 2, 3].map(Scale.degrees(\"C4 major\")) => [\"C4\", \"D4\", \"E4\"]\n */\nexport function degrees(scaleName: string | ScaleNameTokens) {\n const scale = get(scaleName);\n return transposeIntervalSetByDegree(scale.intervals, scale.tonic ?? \"\");\n}\n\nexport default {\n degrees,\n detect,\n extended,\n get,\n modeNames,\n names,\n rangeOf,\n reduced,\n scaleChords,\n scaleNotes,\n tokenize,\n\n // deprecated\n scale,\n};\n"],"mappings":"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","names":["scaleTypeNames","getScaleType","chordTypes","chroma","scaleTypes","scale","names","nums"]}
|
package/dist/index.mjs
CHANGED
|
@@ -8,8 +8,15 @@ import {
|
|
|
8
8
|
transposeIntervalSetByDegree
|
|
9
9
|
} from "@tonaljs/core";
|
|
10
10
|
import { enharmonic, fromMidi, sortedUniqNames } from "@tonaljs/note";
|
|
11
|
-
import { isSubsetOf, isSupersetOf, modes } from "@tonaljs/pcset";
|
|
12
11
|
import {
|
|
12
|
+
chroma,
|
|
13
|
+
isChroma,
|
|
14
|
+
isSubsetOf,
|
|
15
|
+
isSupersetOf,
|
|
16
|
+
modes
|
|
17
|
+
} from "@tonaljs/pcset";
|
|
18
|
+
import {
|
|
19
|
+
all,
|
|
13
20
|
all as scaleTypes,
|
|
14
21
|
get as getScaleType,
|
|
15
22
|
names as scaleTypeNames
|
|
@@ -53,14 +60,37 @@ function get(src) {
|
|
|
53
60
|
return { ...st, name, type, tonic, notes };
|
|
54
61
|
}
|
|
55
62
|
var scale = deprecate("Scale.scale", "Scale.get", get);
|
|
63
|
+
function detect(notes, options = {}) {
|
|
64
|
+
const notesChroma = chroma(notes);
|
|
65
|
+
const tonic = note(options.tonic ?? notes[0] ?? "");
|
|
66
|
+
const tonicChroma = tonic.chroma;
|
|
67
|
+
if (tonicChroma === void 0) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
const pitchClasses = notesChroma.split("");
|
|
71
|
+
pitchClasses[tonicChroma] = "1";
|
|
72
|
+
const scaleChroma = rotate(tonicChroma, pitchClasses).join("");
|
|
73
|
+
const match = all().find((scaleType) => scaleType.chroma === scaleChroma);
|
|
74
|
+
const results = [];
|
|
75
|
+
if (match) {
|
|
76
|
+
results.push(tonic.name + " " + match.name);
|
|
77
|
+
}
|
|
78
|
+
if (options.match === "exact") {
|
|
79
|
+
return results;
|
|
80
|
+
}
|
|
81
|
+
extended(scaleChroma).forEach((scaleName) => {
|
|
82
|
+
results.push(tonic.name + " " + scaleName);
|
|
83
|
+
});
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
56
86
|
function scaleChords(name) {
|
|
57
87
|
const s = get(name);
|
|
58
88
|
const inScale = isSubsetOf(s.chroma);
|
|
59
89
|
return chordTypes().filter((chord) => inScale(chord.chroma)).map((chord) => chord.aliases[0]);
|
|
60
90
|
}
|
|
61
91
|
function extended(name) {
|
|
62
|
-
const
|
|
63
|
-
const isSuperset = isSupersetOf(
|
|
92
|
+
const chroma2 = isChroma(name) ? name : get(name).chroma;
|
|
93
|
+
const isSuperset = isSupersetOf(chroma2);
|
|
64
94
|
return scaleTypes().filter((scale2) => isSuperset(scale2.chroma)).map((scale2) => scale2.name);
|
|
65
95
|
}
|
|
66
96
|
function reduced(name) {
|
|
@@ -79,8 +109,8 @@ function modeNames(name) {
|
|
|
79
109
|
return [];
|
|
80
110
|
}
|
|
81
111
|
const tonics = s.tonic ? s.notes : s.intervals;
|
|
82
|
-
return modes(s.chroma).map((
|
|
83
|
-
const modeName = get(
|
|
112
|
+
return modes(s.chroma).map((chroma2, i) => {
|
|
113
|
+
const modeName = get(chroma2).name;
|
|
84
114
|
return modeName ? [tonics[i], modeName] : ["", ""];
|
|
85
115
|
}).filter((x) => x[0]);
|
|
86
116
|
}
|
|
@@ -92,8 +122,8 @@ function getNoteNameOf(scale2) {
|
|
|
92
122
|
const height = currNote.height;
|
|
93
123
|
if (height === void 0)
|
|
94
124
|
return void 0;
|
|
95
|
-
const
|
|
96
|
-
const position = chromas.indexOf(
|
|
125
|
+
const chroma2 = height % 12;
|
|
126
|
+
const position = chromas.indexOf(chroma2);
|
|
97
127
|
if (position === -1)
|
|
98
128
|
return void 0;
|
|
99
129
|
return enharmonic(currNote.name, names2[position]);
|
|
@@ -114,21 +144,23 @@ function degrees(scaleName) {
|
|
|
114
144
|
return transposeIntervalSetByDegree(scale2.intervals, scale2.tonic ?? "");
|
|
115
145
|
}
|
|
116
146
|
var scale_default = {
|
|
117
|
-
|
|
118
|
-
|
|
147
|
+
degrees,
|
|
148
|
+
detect,
|
|
119
149
|
extended,
|
|
150
|
+
get,
|
|
120
151
|
modeNames,
|
|
152
|
+
names,
|
|
153
|
+
rangeOf,
|
|
121
154
|
reduced,
|
|
122
155
|
scaleChords,
|
|
123
156
|
scaleNotes,
|
|
124
157
|
tokenize,
|
|
125
|
-
rangeOf,
|
|
126
|
-
degrees,
|
|
127
158
|
scale
|
|
128
159
|
};
|
|
129
160
|
export {
|
|
130
161
|
scale_default as default,
|
|
131
162
|
degrees,
|
|
163
|
+
detect,
|
|
132
164
|
extended,
|
|
133
165
|
get,
|
|
134
166
|
modeNames,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts"],"sourcesContent":["/**\n * References:\n * - https://www.researchgate.net/publication/327567188_An_Algorithm_for_Spelling_the_Pitches_of_Any_Musical_Scale\n * @module scale\n */\nimport { all as chordTypes } from \"@tonaljs/chord-type\";\nimport { range as nums, rotate } from \"@tonaljs/collection\";\nimport {\n deprecate,\n note,\n NoteName,\n transpose,\n transposeIntervalSetByDegree,\n} from \"@tonaljs/core\";\nimport { enharmonic, fromMidi, sortedUniqNames } from \"@tonaljs/note\";\nimport { isSubsetOf, isSupersetOf, modes } from \"@tonaljs/pcset\";\nimport {\n all as scaleTypes,\n get as getScaleType,\n names as scaleTypeNames,\n ScaleType,\n} from \"@tonaljs/scale-type\";\n\ntype ScaleName = string;\ntype ScaleNameTokens = [string, string]; // [TONIC, SCALE TYPE]\n\nexport interface Scale extends ScaleType {\n tonic: string | null;\n type: string;\n notes: NoteName[];\n}\n\nconst NoScale: Scale = {\n empty: true,\n name: \"\",\n type: \"\",\n tonic: null,\n setNum: NaN,\n chroma: \"\",\n normalized: \"\",\n aliases: [],\n notes: [],\n intervals: [],\n};\n\n/**\n * Given a string with a scale name and (optionally) a tonic, split\n * that components.\n *\n * It retuns an array with the form [ name, tonic ] where tonic can be a\n * note name or null and name can be any arbitrary string\n * (this function doesn\"t check if that scale name exists)\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array} an array [tonic, name]\n * @example\n * tokenize(\"C mixolydean\") // => [\"C\", \"mixolydean\"]\n * tokenize(\"anything is valid\") // => [\"\", \"anything is valid\"]\n * tokenize() // => [\"\", \"\"]\n */\nexport function tokenize(name: ScaleName): ScaleNameTokens {\n if (typeof name !== \"string\") {\n return [\"\", \"\"];\n }\n const i = name.indexOf(\" \");\n const tonic = note(name.substring(0, i));\n if (tonic.empty) {\n const n = note(name);\n return n.empty ? [\"\", name] : [n.name, \"\"];\n }\n\n const type = name.substring(tonic.name.length + 1);\n return [tonic.name, type.length ? type : \"\"];\n}\n\n/**\n * Get all scale names\n * @function\n */\nexport const names = scaleTypeNames;\n\n/**\n * Get a Scale from a scale name.\n */\nexport function get(src: ScaleName | ScaleNameTokens): Scale {\n const tokens = Array.isArray(src) ? src : tokenize(src);\n const tonic = note(tokens[0]).name;\n const st = getScaleType(tokens[1]);\n if (st.empty) {\n return NoScale;\n }\n\n const type = st.name;\n const notes: string[] = tonic\n ? st.intervals.map((i) => transpose(tonic, i))\n : [];\n\n const name = tonic ? tonic + \" \" + type : type;\n\n return { ...st, name, type, tonic, notes };\n}\n\nexport const scale = deprecate(\"Scale.scale\", \"Scale.get\", get);\n\n/**\n * Get all chords that fits a given scale\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array<string>} - the chord names\n *\n * @example\n * scaleChords(\"pentatonic\") // => [\"5\", \"64\", \"M\", \"M6\", \"Madd9\", \"Msus2\"]\n */\nexport function scaleChords(name: string): string[] {\n const s = get(name);\n const inScale = isSubsetOf(s.chroma);\n return chordTypes()\n .filter((chord) => inScale(chord.chroma))\n .map((chord) => chord.aliases[0]);\n}\n/**\n * Get all scales names that are a superset of the given one\n * (has the same notes and at least one more)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n * @example\n * extended(\"major\") // => [\"bebop\", \"bebop dominant\", \"bebop major\", \"chromatic\", \"ichikosucho\"]\n */\nexport function extended(name: string): string[] {\n const s = get(name);\n const isSuperset = isSupersetOf(s.chroma);\n return scaleTypes()\n .filter((scale) => isSuperset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Find all scales names that are a subset of the given one\n * (has less notes but all from the given scale)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n *\n * @example\n * reduced(\"major\") // => [\"ionian pentatonic\", \"major pentatonic\", \"ritusen\"]\n */\nexport function reduced(name: string): string[] {\n const isSubset = isSubsetOf(get(name).chroma);\n return scaleTypes()\n .filter((scale) => isSubset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Given an array of notes, return the scale: a pitch class set starting from\n * the first note of the array\n *\n * @function\n * @param {string[]} notes\n * @return {string[]} pitch classes with same tonic\n * @example\n * scaleNotes(['C4', 'c3', 'C5', 'C4', 'c4']) // => [\"C\"]\n * scaleNotes(['D4', 'c#5', 'A5', 'F#6']) // => [\"D\", \"F#\", \"A\", \"C#\"]\n */\nexport function scaleNotes(notes: NoteName[]) {\n const pcset: string[] = notes.map((n) => note(n).pc).filter((x) => x);\n const tonic = pcset[0];\n const scale = sortedUniqNames(pcset);\n return rotate(scale.indexOf(tonic), scale);\n}\n\ntype ScaleMode = [string, string];\n/**\n * Find mode names of a scale\n *\n * @function\n * @param {string} name - scale name\n * @example\n * modeNames(\"C pentatonic\") // => [\n * [\"C\", \"major pentatonic\"],\n * [\"D\", \"egyptian\"],\n * [\"E\", \"malkos raga\"],\n * [\"G\", \"ritusen\"],\n * [\"A\", \"minor pentatonic\"]\n * ]\n */\nexport function modeNames(name: string): ScaleMode[] {\n const s = get(name);\n if (s.empty) {\n return [];\n }\n\n const tonics = s.tonic ? s.notes : s.intervals;\n return modes(s.chroma)\n .map((chroma: string, i: number): ScaleMode => {\n const modeName = get(chroma).name;\n return modeName ? [tonics[i], modeName] : [\"\", \"\"];\n })\n .filter((x) => x[0]);\n}\n\nfunction getNoteNameOf(scale: string | string[]) {\n const names = Array.isArray(scale) ? scaleNotes(scale) : get(scale).notes;\n const chromas = names.map((name) => note(name).chroma);\n\n return (noteOrMidi: string | number): string | undefined => {\n const currNote =\n typeof noteOrMidi === \"number\"\n ? note(fromMidi(noteOrMidi))\n : note(noteOrMidi);\n const height = currNote.height;\n\n if (height === undefined) return undefined;\n const chroma = height % 12;\n const position = chromas.indexOf(chroma);\n if (position === -1) return undefined;\n return enharmonic(currNote.name, names[position]);\n };\n}\n\nexport function rangeOf(scale: string | string[]) {\n const getName = getNoteNameOf(scale);\n return (fromNote: string, toNote: string) => {\n const from = note(fromNote).height;\n const to = note(toNote).height;\n if (from === undefined || to === undefined) return [];\n\n return nums(from, to)\n .map(getName)\n .filter((x) => x);\n };\n}\n\n/**\n * Returns a function to get a note name from the scale degree.\n *\n * @example\n * [1, 2, 3].map(Scale.degrees(\"C major\")) => [\"C\", \"D\", \"E\"]\n * [1, 2, 3].map(Scale.degrees(\"C4 major\")) => [\"C4\", \"D4\", \"E4\"]\n */\nexport function degrees(scaleName: string | ScaleNameTokens) {\n const scale = get(scaleName);\n return transposeIntervalSetByDegree(scale.intervals, scale.tonic ?? \"\");\n}\n\nexport default {\n get,\n names,\n extended,\n modeNames,\n reduced,\n scaleChords,\n scaleNotes,\n tokenize,\n rangeOf,\n degrees,\n // deprecated\n scale,\n};\n"],"mappings":"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","names":["scale","names"]}
|
|
1
|
+
{"version":3,"sources":["../index.ts"],"sourcesContent":["/**\n * References:\n * - https://www.researchgate.net/publication/327567188_An_Algorithm_for_Spelling_the_Pitches_of_Any_Musical_Scale\n * @module scale\n */\nimport { all as chordTypes } from \"@tonaljs/chord-type\";\nimport { range as nums, rotate } from \"@tonaljs/collection\";\nimport {\n deprecate,\n note,\n NoteName,\n transpose,\n transposeIntervalSetByDegree,\n} from \"@tonaljs/core\";\nimport { enharmonic, fromMidi, sortedUniqNames } from \"@tonaljs/note\";\nimport {\n chroma,\n isChroma,\n isSubsetOf,\n isSupersetOf,\n modes,\n} from \"@tonaljs/pcset\";\nimport {\n all,\n all as scaleTypes,\n get as getScaleType,\n names as scaleTypeNames,\n ScaleType,\n} from \"@tonaljs/scale-type\";\n\ntype ScaleName = string;\ntype ScaleNameTokens = [string, string]; // [TONIC, SCALE TYPE]\n\nexport interface Scale extends ScaleType {\n tonic: string | null;\n type: string;\n notes: NoteName[];\n}\n\nconst NoScale: Scale = {\n empty: true,\n name: \"\",\n type: \"\",\n tonic: null,\n setNum: NaN,\n chroma: \"\",\n normalized: \"\",\n aliases: [],\n notes: [],\n intervals: [],\n};\n\n/**\n * Given a string with a scale name and (optionally) a tonic, split\n * that components.\n *\n * It retuns an array with the form [ name, tonic ] where tonic can be a\n * note name or null and name can be any arbitrary string\n * (this function doesn\"t check if that scale name exists)\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array} an array [tonic, name]\n * @example\n * tokenize(\"C mixolydean\") // => [\"C\", \"mixolydean\"]\n * tokenize(\"anything is valid\") // => [\"\", \"anything is valid\"]\n * tokenize() // => [\"\", \"\"]\n */\nexport function tokenize(name: ScaleName): ScaleNameTokens {\n if (typeof name !== \"string\") {\n return [\"\", \"\"];\n }\n const i = name.indexOf(\" \");\n const tonic = note(name.substring(0, i));\n if (tonic.empty) {\n const n = note(name);\n return n.empty ? [\"\", name] : [n.name, \"\"];\n }\n\n const type = name.substring(tonic.name.length + 1);\n return [tonic.name, type.length ? type : \"\"];\n}\n\n/**\n * Get all scale names\n * @function\n */\nexport const names = scaleTypeNames;\n\n/**\n * Get a Scale from a scale name.\n */\nexport function get(src: ScaleName | ScaleNameTokens): Scale {\n const tokens = Array.isArray(src) ? src : tokenize(src);\n const tonic = note(tokens[0]).name;\n const st = getScaleType(tokens[1]);\n if (st.empty) {\n return NoScale;\n }\n\n const type = st.name;\n const notes: string[] = tonic\n ? st.intervals.map((i) => transpose(tonic, i))\n : [];\n\n const name = tonic ? tonic + \" \" + type : type;\n\n return { ...st, name, type, tonic, notes };\n}\n\nexport const scale = deprecate(\"Scale.scale\", \"Scale.get\", get);\n\nexport function detect(\n notes: string[],\n options: { tonic?: string; match?: \"exact\" | \"fit\" } = {}\n): string[] {\n const notesChroma = chroma(notes);\n const tonic = note(options.tonic ?? notes[0] ?? \"\");\n const tonicChroma = tonic.chroma;\n if (tonicChroma === undefined) {\n return [];\n }\n\n const pitchClasses = notesChroma.split(\"\");\n pitchClasses[tonicChroma] = \"1\";\n const scaleChroma = rotate(tonicChroma, pitchClasses).join(\"\");\n const match = all().find((scaleType) => scaleType.chroma === scaleChroma);\n\n const results: string[] = [];\n if (match) {\n results.push(tonic.name + \" \" + match.name);\n }\n if (options.match === \"exact\") {\n return results;\n }\n\n extended(scaleChroma).forEach((scaleName) => {\n results.push(tonic.name + \" \" + scaleName);\n });\n\n return results;\n}\n\n/**\n * Get all chords that fits a given scale\n *\n * @function\n * @param {string} name - the scale name\n * @return {Array<string>} - the chord names\n *\n * @example\n * scaleChords(\"pentatonic\") // => [\"5\", \"64\", \"M\", \"M6\", \"Madd9\", \"Msus2\"]\n */\nexport function scaleChords(name: string): string[] {\n const s = get(name);\n const inScale = isSubsetOf(s.chroma);\n return chordTypes()\n .filter((chord) => inScale(chord.chroma))\n .map((chord) => chord.aliases[0]);\n}\n/**\n * Get all scales names that are a superset of the given one\n * (has the same notes and at least one more)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n * @example\n * extended(\"major\") // => [\"bebop\", \"bebop dominant\", \"bebop major\", \"chromatic\", \"ichikosucho\"]\n */\nexport function extended(name: string): string[] {\n const chroma = isChroma(name) ? name : get(name).chroma;\n const isSuperset = isSupersetOf(chroma);\n return scaleTypes()\n .filter((scale) => isSuperset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Find all scales names that are a subset of the given one\n * (has less notes but all from the given scale)\n *\n * @function\n * @param {string} name\n * @return {Array} a list of scale names\n *\n * @example\n * reduced(\"major\") // => [\"ionian pentatonic\", \"major pentatonic\", \"ritusen\"]\n */\nexport function reduced(name: string): string[] {\n const isSubset = isSubsetOf(get(name).chroma);\n return scaleTypes()\n .filter((scale) => isSubset(scale.chroma))\n .map((scale) => scale.name);\n}\n\n/**\n * Given an array of notes, return the scale: a pitch class set starting from\n * the first note of the array\n *\n * @function\n * @param {string[]} notes\n * @return {string[]} pitch classes with same tonic\n * @example\n * scaleNotes(['C4', 'c3', 'C5', 'C4', 'c4']) // => [\"C\"]\n * scaleNotes(['D4', 'c#5', 'A5', 'F#6']) // => [\"D\", \"F#\", \"A\", \"C#\"]\n */\nexport function scaleNotes(notes: NoteName[]) {\n const pcset: string[] = notes.map((n) => note(n).pc).filter((x) => x);\n const tonic = pcset[0];\n const scale = sortedUniqNames(pcset);\n return rotate(scale.indexOf(tonic), scale);\n}\n\ntype ScaleMode = [string, string];\n/**\n * Find mode names of a scale\n *\n * @function\n * @param {string} name - scale name\n * @example\n * modeNames(\"C pentatonic\") // => [\n * [\"C\", \"major pentatonic\"],\n * [\"D\", \"egyptian\"],\n * [\"E\", \"malkos raga\"],\n * [\"G\", \"ritusen\"],\n * [\"A\", \"minor pentatonic\"]\n * ]\n */\nexport function modeNames(name: string): ScaleMode[] {\n const s = get(name);\n if (s.empty) {\n return [];\n }\n\n const tonics = s.tonic ? s.notes : s.intervals;\n return modes(s.chroma)\n .map((chroma: string, i: number): ScaleMode => {\n const modeName = get(chroma).name;\n return modeName ? [tonics[i], modeName] : [\"\", \"\"];\n })\n .filter((x) => x[0]);\n}\n\nfunction getNoteNameOf(scale: string | string[]) {\n const names = Array.isArray(scale) ? scaleNotes(scale) : get(scale).notes;\n const chromas = names.map((name) => note(name).chroma);\n\n return (noteOrMidi: string | number): string | undefined => {\n const currNote =\n typeof noteOrMidi === \"number\"\n ? note(fromMidi(noteOrMidi))\n : note(noteOrMidi);\n const height = currNote.height;\n\n if (height === undefined) return undefined;\n const chroma = height % 12;\n const position = chromas.indexOf(chroma);\n if (position === -1) return undefined;\n return enharmonic(currNote.name, names[position]);\n };\n}\n\nexport function rangeOf(scale: string | string[]) {\n const getName = getNoteNameOf(scale);\n return (fromNote: string, toNote: string) => {\n const from = note(fromNote).height;\n const to = note(toNote).height;\n if (from === undefined || to === undefined) return [];\n\n return nums(from, to)\n .map(getName)\n .filter((x) => x);\n };\n}\n\n/**\n * Returns a function to get a note name from the scale degree.\n *\n * @example\n * [1, 2, 3].map(Scale.degrees(\"C major\")) => [\"C\", \"D\", \"E\"]\n * [1, 2, 3].map(Scale.degrees(\"C4 major\")) => [\"C4\", \"D4\", \"E4\"]\n */\nexport function degrees(scaleName: string | ScaleNameTokens) {\n const scale = get(scaleName);\n return transposeIntervalSetByDegree(scale.intervals, scale.tonic ?? \"\");\n}\n\nexport default {\n degrees,\n detect,\n extended,\n get,\n modeNames,\n names,\n rangeOf,\n reduced,\n scaleChords,\n scaleNotes,\n tokenize,\n\n // deprecated\n scale,\n};\n"],"mappings":"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","names":["chroma","scale","names"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonaljs/scale",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.0",
|
|
4
4
|
"description": "Musical scales and its relations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scale",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"@tonaljs/collection": "^4.8.0",
|
|
21
21
|
"@tonaljs/core": "^4.9.0",
|
|
22
22
|
"@tonaljs/note": "^4.10.0",
|
|
23
|
-
"@tonaljs/pcset": "^4.8.
|
|
24
|
-
"@tonaljs/scale-type": "^4.8.
|
|
23
|
+
"@tonaljs/pcset": "^4.8.1",
|
|
24
|
+
"@tonaljs/scale-type": "^4.8.1"
|
|
25
25
|
},
|
|
26
26
|
"author": "danigb@gmail.com",
|
|
27
27
|
"license": "MIT",
|