@tonaljs/scale 4.10.0 → 4.12.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 +40 -2
- package/dist/index.d.ts +15 -5
- package/dist/index.js +49 -15
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +54 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
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,10 +160,21 @@ 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
|
|
|
167
|
+
### `Scale.steps(scaleName: string) => (degree: number) => string`
|
|
168
|
+
|
|
169
|
+
Same as `Scale.degree` but 0 is tonic. It plays better with ranges:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
import { Range, Scale } from "tonal";
|
|
173
|
+
|
|
174
|
+
Range.numeric([-3, 3]).map(Scale.steps("C4 major"));
|
|
175
|
+
// => ["G3", "A3", "B3", "C4", "D4", "E4", "F4"]
|
|
176
|
+
```
|
|
177
|
+
|
|
140
178
|
### `Scale.rangeOf(scaleName: string) => (from: string, to: string) => string[]`
|
|
141
179
|
|
|
142
180
|
`Scale.rangeOf` returns a function to create scale ranges:
|
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
|
*
|
|
@@ -106,18 +110,24 @@ declare function rangeOf(scale: string | string[]): (fromNote: string, toNote: s
|
|
|
106
110
|
* [1, 2, 3].map(Scale.degrees("C4 major")) => ["C4", "D4", "E4"]
|
|
107
111
|
*/
|
|
108
112
|
declare function degrees(scaleName: string | ScaleNameTokens): (degree: number) => string;
|
|
113
|
+
/**
|
|
114
|
+
* Sames as `degree` but with 0-based index
|
|
115
|
+
*/
|
|
116
|
+
declare function steps(scaleName: string | ScaleNameTokens): (normalized: number) => string;
|
|
109
117
|
declare const _default: {
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
degrees: typeof degrees;
|
|
119
|
+
detect: typeof detect;
|
|
112
120
|
extended: typeof extended;
|
|
121
|
+
get: typeof get;
|
|
113
122
|
modeNames: typeof modeNames;
|
|
123
|
+
names: typeof names$1;
|
|
124
|
+
rangeOf: typeof rangeOf;
|
|
114
125
|
reduced: typeof reduced;
|
|
115
126
|
scaleChords: typeof scaleChords;
|
|
116
127
|
scaleNotes: typeof scaleNotes;
|
|
128
|
+
steps: typeof steps;
|
|
117
129
|
tokenize: typeof tokenize;
|
|
118
|
-
rangeOf: typeof rangeOf;
|
|
119
|
-
degrees: typeof degrees;
|
|
120
130
|
scale: (this: unknown, ...args: unknown[]) => Scale;
|
|
121
131
|
};
|
|
122
132
|
|
|
123
|
-
export { Scale, _default as default, degrees, extended, get, modeNames, names, rangeOf, reduced, scale, scaleChords, scaleNotes, tokenize };
|
|
133
|
+
export { Scale, _default as default, degrees, detect, extended, get, modeNames, names, rangeOf, reduced, scale, scaleChords, scaleNotes, steps, 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,
|
|
@@ -31,6 +32,7 @@ __export(scale_exports, {
|
|
|
31
32
|
scale: () => scale,
|
|
32
33
|
scaleChords: () => scaleChords,
|
|
33
34
|
scaleNotes: () => scaleNotes,
|
|
35
|
+
steps: () => steps,
|
|
34
36
|
tokenize: () => tokenize
|
|
35
37
|
});
|
|
36
38
|
module.exports = __toCommonJS(scale_exports);
|
|
@@ -79,14 +81,37 @@ function get(src) {
|
|
|
79
81
|
return { ...st, name, type, tonic, notes };
|
|
80
82
|
}
|
|
81
83
|
var scale = (0, import_core.deprecate)("Scale.scale", "Scale.get", get);
|
|
84
|
+
function detect(notes, options = {}) {
|
|
85
|
+
const notesChroma = (0, import_pcset.chroma)(notes);
|
|
86
|
+
const tonic = (0, import_core.note)(options.tonic ?? notes[0] ?? "");
|
|
87
|
+
const tonicChroma = tonic.chroma;
|
|
88
|
+
if (tonicChroma === void 0) {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
const pitchClasses = notesChroma.split("");
|
|
92
|
+
pitchClasses[tonicChroma] = "1";
|
|
93
|
+
const scaleChroma = (0, import_collection.rotate)(tonicChroma, pitchClasses).join("");
|
|
94
|
+
const match = (0, import_scale_type.all)().find((scaleType) => scaleType.chroma === scaleChroma);
|
|
95
|
+
const results = [];
|
|
96
|
+
if (match) {
|
|
97
|
+
results.push(tonic.name + " " + match.name);
|
|
98
|
+
}
|
|
99
|
+
if (options.match === "exact") {
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
extended(scaleChroma).forEach((scaleName) => {
|
|
103
|
+
results.push(tonic.name + " " + scaleName);
|
|
104
|
+
});
|
|
105
|
+
return results;
|
|
106
|
+
}
|
|
82
107
|
function scaleChords(name) {
|
|
83
108
|
const s = get(name);
|
|
84
109
|
const inScale = (0, import_pcset.isSubsetOf)(s.chroma);
|
|
85
110
|
return (0, import_chord_type.all)().filter((chord) => inScale(chord.chroma)).map((chord) => chord.aliases[0]);
|
|
86
111
|
}
|
|
87
112
|
function extended(name) {
|
|
88
|
-
const
|
|
89
|
-
const isSuperset = (0, import_pcset.isSupersetOf)(
|
|
113
|
+
const chroma2 = (0, import_pcset.isChroma)(name) ? name : get(name).chroma;
|
|
114
|
+
const isSuperset = (0, import_pcset.isSupersetOf)(chroma2);
|
|
90
115
|
return (0, import_scale_type.all)().filter((scale2) => isSuperset(scale2.chroma)).map((scale2) => scale2.name);
|
|
91
116
|
}
|
|
92
117
|
function reduced(name) {
|
|
@@ -105,8 +130,8 @@ function modeNames(name) {
|
|
|
105
130
|
return [];
|
|
106
131
|
}
|
|
107
132
|
const tonics = s.tonic ? s.notes : s.intervals;
|
|
108
|
-
return (0, import_pcset.modes)(s.chroma).map((
|
|
109
|
-
const modeName = get(
|
|
133
|
+
return (0, import_pcset.modes)(s.chroma).map((chroma2, i) => {
|
|
134
|
+
const modeName = get(chroma2).name;
|
|
110
135
|
return modeName ? [tonics[i], modeName] : ["", ""];
|
|
111
136
|
}).filter((x) => x[0]);
|
|
112
137
|
}
|
|
@@ -118,8 +143,8 @@ function getNoteNameOf(scale2) {
|
|
|
118
143
|
const height = currNote.height;
|
|
119
144
|
if (height === void 0)
|
|
120
145
|
return void 0;
|
|
121
|
-
const
|
|
122
|
-
const position = chromas.indexOf(
|
|
146
|
+
const chroma2 = height % 12;
|
|
147
|
+
const position = chromas.indexOf(chroma2);
|
|
123
148
|
if (position === -1)
|
|
124
149
|
return void 0;
|
|
125
150
|
return (0, import_note.enharmonic)(currNote.name, names2[position]);
|
|
@@ -136,25 +161,33 @@ function rangeOf(scale2) {
|
|
|
136
161
|
};
|
|
137
162
|
}
|
|
138
163
|
function degrees(scaleName) {
|
|
139
|
-
const
|
|
140
|
-
|
|
164
|
+
const { intervals, tonic } = get(scaleName);
|
|
165
|
+
const transpose2 = (0, import_core.tonicIntervalsTransposer)(intervals, tonic);
|
|
166
|
+
return (degree) => degree ? transpose2(degree > 0 ? degree - 1 : degree) : "";
|
|
167
|
+
}
|
|
168
|
+
function steps(scaleName) {
|
|
169
|
+
const { intervals, tonic } = get(scaleName);
|
|
170
|
+
return (0, import_core.tonicIntervalsTransposer)(intervals, tonic);
|
|
141
171
|
}
|
|
142
172
|
var scale_default = {
|
|
143
|
-
|
|
144
|
-
|
|
173
|
+
degrees,
|
|
174
|
+
detect,
|
|
145
175
|
extended,
|
|
176
|
+
get,
|
|
146
177
|
modeNames,
|
|
178
|
+
names,
|
|
179
|
+
rangeOf,
|
|
147
180
|
reduced,
|
|
148
181
|
scaleChords,
|
|
149
182
|
scaleNotes,
|
|
183
|
+
steps,
|
|
150
184
|
tokenize,
|
|
151
|
-
rangeOf,
|
|
152
|
-
degrees,
|
|
153
185
|
scale
|
|
154
186
|
};
|
|
155
187
|
// Annotate the CommonJS export names for ESM import in node:
|
|
156
188
|
0 && (module.exports = {
|
|
157
189
|
degrees,
|
|
190
|
+
detect,
|
|
158
191
|
extended,
|
|
159
192
|
get,
|
|
160
193
|
modeNames,
|
|
@@ -164,6 +197,7 @@ var scale_default = {
|
|
|
164
197
|
scale,
|
|
165
198
|
scaleChords,
|
|
166
199
|
scaleNotes,
|
|
200
|
+
steps,
|
|
167
201
|
tokenize
|
|
168
202
|
});
|
|
169
203
|
//# sourceMappingURL=index.js.map
|
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 tonicIntervalsTransposer,\n transpose,\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 { intervals, tonic } = get(scaleName);\n const transpose = tonicIntervalsTransposer(intervals, tonic);\n return (degree: number) =>\n degree ? transpose(degree > 0 ? degree - 1 : degree) : \"\";\n}\n\n/**\n * Sames as `degree` but with 0-based index\n */\nexport function steps(scaleName: string | ScaleNameTokens) {\n const { intervals, tonic } = get(scaleName);\n return tonicIntervalsTransposer(intervals, 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 steps,\n tokenize,\n\n // deprecated\n scale,\n};\n"],"mappings":"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","names":["scaleTypeNames","getScaleType","chordTypes","chroma","scaleTypes","scale","names","nums","transpose"]}
|
package/dist/index.mjs
CHANGED
|
@@ -4,12 +4,19 @@ import { range as nums, rotate } from "@tonaljs/collection";
|
|
|
4
4
|
import {
|
|
5
5
|
deprecate,
|
|
6
6
|
note,
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
tonicIntervalsTransposer,
|
|
8
|
+
transpose
|
|
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]);
|
|
@@ -110,25 +140,33 @@ function rangeOf(scale2) {
|
|
|
110
140
|
};
|
|
111
141
|
}
|
|
112
142
|
function degrees(scaleName) {
|
|
113
|
-
const
|
|
114
|
-
|
|
143
|
+
const { intervals, tonic } = get(scaleName);
|
|
144
|
+
const transpose2 = tonicIntervalsTransposer(intervals, tonic);
|
|
145
|
+
return (degree) => degree ? transpose2(degree > 0 ? degree - 1 : degree) : "";
|
|
146
|
+
}
|
|
147
|
+
function steps(scaleName) {
|
|
148
|
+
const { intervals, tonic } = get(scaleName);
|
|
149
|
+
return tonicIntervalsTransposer(intervals, tonic);
|
|
115
150
|
}
|
|
116
151
|
var scale_default = {
|
|
117
|
-
|
|
118
|
-
|
|
152
|
+
degrees,
|
|
153
|
+
detect,
|
|
119
154
|
extended,
|
|
155
|
+
get,
|
|
120
156
|
modeNames,
|
|
157
|
+
names,
|
|
158
|
+
rangeOf,
|
|
121
159
|
reduced,
|
|
122
160
|
scaleChords,
|
|
123
161
|
scaleNotes,
|
|
162
|
+
steps,
|
|
124
163
|
tokenize,
|
|
125
|
-
rangeOf,
|
|
126
|
-
degrees,
|
|
127
164
|
scale
|
|
128
165
|
};
|
|
129
166
|
export {
|
|
130
167
|
scale_default as default,
|
|
131
168
|
degrees,
|
|
169
|
+
detect,
|
|
132
170
|
extended,
|
|
133
171
|
get,
|
|
134
172
|
modeNames,
|
|
@@ -138,6 +176,7 @@ export {
|
|
|
138
176
|
scale,
|
|
139
177
|
scaleChords,
|
|
140
178
|
scaleNotes,
|
|
179
|
+
steps,
|
|
141
180
|
tokenize
|
|
142
181
|
};
|
|
143
182
|
//# sourceMappingURL=index.mjs.map
|
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 tonicIntervalsTransposer,\n transpose,\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 { intervals, tonic } = get(scaleName);\n const transpose = tonicIntervalsTransposer(intervals, tonic);\n return (degree: number) =>\n degree ? transpose(degree > 0 ? degree - 1 : degree) : \"\";\n}\n\n/**\n * Sames as `degree` but with 0-based index\n */\nexport function steps(scaleName: string | ScaleNameTokens) {\n const { intervals, tonic } = get(scaleName);\n return tonicIntervalsTransposer(intervals, 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 steps,\n tokenize,\n\n // deprecated\n scale,\n};\n"],"mappings":"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","names":["chroma","scale","names","transpose"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tonaljs/scale",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"description": "Musical scales and its relations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"scale",
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@tonaljs/chord-type": "^4.8.0",
|
|
20
20
|
"@tonaljs/collection": "^4.8.0",
|
|
21
|
-
"@tonaljs/core": "^4.
|
|
21
|
+
"@tonaljs/core": "^4.10.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",
|