@tonaljs/scale 4.7.3 → 4.9.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 CHANGED
@@ -103,6 +103,38 @@ Scale.modeNames("C pentatonic"); // => [
103
103
  // ]
104
104
  ```
105
105
 
106
+ ### `Scale.degrees(scaleName: string) => (degree: number) => string`
107
+
108
+ `Scale.degrees` returns a function to get a note name from a scale degree:
109
+
110
+ ```js
111
+ const c4major = Scale.degrees("C4 major");
112
+ c4major(1); // => "C4"
113
+ c4major(2); // => "D4"
114
+ c4major(8); // => "C5"
115
+ c4major(-1); // => "B3"
116
+ c4major(-3); // => "A3"
117
+ c4major(-7); // => "C2"
118
+ ```
119
+
120
+ Bear in mind that degree numbers starts with 1 and 0 returns an empty string:
121
+
122
+ ```js
123
+ c4major(0); // => ""
124
+ ```
125
+
126
+ Because it returns a function, it's handy to be used with `map` (and similar functions):
127
+
128
+ ```js
129
+ [1, 2, 3].map(Scale.degrees("C major")) => ["C", "D", "E"]
130
+ [1, 2, 3].map(Scale.degrees("C4 major")) => ["C4", "D4", "E4"]
131
+ [-1, -2, -3].map(Scale.degrees("C major")) => ["B", "A", "G"]
132
+ ```
133
+
134
+ Notice that it uses octaves if the scale tonic has an octave or pitch classes (_octaveless_ notes) otherwise.
135
+
136
+ See https://en.wikipedia.org/wiki/Degree_(music)
137
+
106
138
  ### `Scale.rangeOf(scaleName: string) => (from: string, to: string) => string[]`
107
139
 
108
140
  `Scale.rangeOf` returns a function to create scale ranges:
package/dist/index.d.ts CHANGED
@@ -98,6 +98,7 @@ type ScaleMode = [string, string];
98
98
  */
99
99
  declare function modeNames(name: string): ScaleMode[];
100
100
  declare function rangeOf(scale: string | string[]): (fromNote: string, toNote: string) => (string | undefined)[];
101
+ declare function degrees(scaleName: string | ScaleNameTokens): (degree: number) => string | undefined;
101
102
  declare const _default: {
102
103
  get: typeof get;
103
104
  names: typeof names$1;
@@ -108,7 +109,8 @@ declare const _default: {
108
109
  scaleNotes: typeof scaleNotes;
109
110
  tokenize: typeof tokenize;
110
111
  rangeOf: typeof rangeOf;
112
+ degrees: typeof degrees;
111
113
  scale: (this: unknown, ...args: unknown[]) => Scale;
112
114
  };
113
115
 
114
- export { Scale, _default as default, extended, get, modeNames, names, rangeOf, reduced, scale, scaleChords, scaleNotes, tokenize };
116
+ export { Scale, _default as default, degrees, extended, get, modeNames, names, rangeOf, reduced, scale, scaleChords, scaleNotes, tokenize };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var scale_exports = {};
22
22
  __export(scale_exports, {
23
23
  default: () => scale_default,
24
+ degrees: () => degrees,
24
25
  extended: () => extended,
25
26
  get: () => get,
26
27
  modeNames: () => modeNames,
@@ -134,6 +135,21 @@ function rangeOf(scale2) {
134
135
  return (0, import_collection.range)(from, to).map(getName).filter((x) => x);
135
136
  };
136
137
  }
138
+ function degrees(scaleName) {
139
+ const scale2 = get(scaleName);
140
+ const intervals = scale2.intervals;
141
+ const len = intervals.length;
142
+ const tonic = scale2.tonic;
143
+ return (degree) => {
144
+ if (!tonic || degree === 0)
145
+ return void 0;
146
+ const normalized = degree > 0 ? degree - 1 : degree;
147
+ const index = normalized < 0 ? len - -normalized % len : normalized % len;
148
+ const octaves = Math.floor(normalized / len);
149
+ const root = (0, import_note.transposeOctaves)(tonic, octaves);
150
+ return (0, import_core.transpose)(root, intervals[index]);
151
+ };
152
+ }
137
153
  var scale_default = {
138
154
  get,
139
155
  names,
@@ -144,10 +160,12 @@ var scale_default = {
144
160
  scaleNotes,
145
161
  tokenize,
146
162
  rangeOf,
163
+ degrees,
147
164
  scale
148
165
  };
149
166
  // Annotate the CommonJS export names for ESM import in node:
150
167
  0 && (module.exports = {
168
+ degrees,
151
169
  extended,
152
170
  get,
153
171
  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 { rotate, range as nums } from \"@tonaljs/collection\";\nimport { deprecate, note, NoteName, transpose } from \"@tonaljs/core\";\nimport { sortedUniqNames, fromMidi, enharmonic } 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\nexport default {\n get,\n names,\n extended,\n modeNames,\n reduced,\n scaleChords,\n scaleNotes,\n tokenize,\n rangeOf,\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 { deprecate, note, NoteName, transpose } from \"@tonaljs/core\";\nimport {\n enharmonic,\n fromMidi,\n sortedUniqNames,\n transposeOctaves,\n} 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\nexport function degrees(scaleName: string | ScaleNameTokens) {\n const scale = get(scaleName);\n const intervals = scale.intervals;\n const len = intervals.length;\n const tonic = scale.tonic;\n return (degree: number) => {\n if (!tonic || degree === 0) return undefined;\n const normalized = degree > 0 ? degree - 1 : degree;\n const index = normalized < 0 ? len - (-normalized % len) : normalized % len;\n const octaves = Math.floor(normalized / len);\n const root = transposeOctaves(tonic, octaves);\n return transpose(root, intervals[index]);\n };\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"]}
package/dist/index.mjs CHANGED
@@ -1,8 +1,13 @@
1
1
  // index.ts
2
2
  import { all as chordTypes } from "@tonaljs/chord-type";
3
- import { rotate, range as nums } from "@tonaljs/collection";
3
+ import { range as nums, rotate } from "@tonaljs/collection";
4
4
  import { deprecate, note, transpose } from "@tonaljs/core";
5
- import { sortedUniqNames, fromMidi, enharmonic } from "@tonaljs/note";
5
+ import {
6
+ enharmonic,
7
+ fromMidi,
8
+ sortedUniqNames,
9
+ transposeOctaves
10
+ } from "@tonaljs/note";
6
11
  import { isSubsetOf, isSupersetOf, modes } from "@tonaljs/pcset";
7
12
  import {
8
13
  all as scaleTypes,
@@ -104,6 +109,21 @@ function rangeOf(scale2) {
104
109
  return nums(from, to).map(getName).filter((x) => x);
105
110
  };
106
111
  }
112
+ function degrees(scaleName) {
113
+ const scale2 = get(scaleName);
114
+ const intervals = scale2.intervals;
115
+ const len = intervals.length;
116
+ const tonic = scale2.tonic;
117
+ return (degree) => {
118
+ if (!tonic || degree === 0)
119
+ return void 0;
120
+ const normalized = degree > 0 ? degree - 1 : degree;
121
+ const index = normalized < 0 ? len - -normalized % len : normalized % len;
122
+ const octaves = Math.floor(normalized / len);
123
+ const root = transposeOctaves(tonic, octaves);
124
+ return transpose(root, intervals[index]);
125
+ };
126
+ }
107
127
  var scale_default = {
108
128
  get,
109
129
  names,
@@ -114,10 +134,12 @@ var scale_default = {
114
134
  scaleNotes,
115
135
  tokenize,
116
136
  rangeOf,
137
+ degrees,
117
138
  scale
118
139
  };
119
140
  export {
120
141
  scale_default as default,
142
+ degrees,
121
143
  extended,
122
144
  get,
123
145
  modeNames,
@@ -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 { rotate, range as nums } from \"@tonaljs/collection\";\nimport { deprecate, note, NoteName, transpose } from \"@tonaljs/core\";\nimport { sortedUniqNames, fromMidi, enharmonic } 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\nexport default {\n get,\n names,\n extended,\n modeNames,\n reduced,\n scaleChords,\n scaleNotes,\n tokenize,\n rangeOf,\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 { deprecate, note, NoteName, transpose } from \"@tonaljs/core\";\nimport {\n enharmonic,\n fromMidi,\n sortedUniqNames,\n transposeOctaves,\n} 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\nexport function degrees(scaleName: string | ScaleNameTokens) {\n const scale = get(scaleName);\n const intervals = scale.intervals;\n const len = intervals.length;\n const tonic = scale.tonic;\n return (degree: number) => {\n if (!tonic || degree === 0) return undefined;\n const normalized = degree > 0 ? degree - 1 : degree;\n const index = normalized < 0 ? len - (-normalized % len) : normalized % len;\n const octaves = Math.floor(normalized / len);\n const root = transposeOctaves(tonic, octaves);\n return transpose(root, intervals[index]);\n };\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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tonaljs/scale",
3
- "version": "4.7.3",
3
+ "version": "4.9.0",
4
4
  "description": "Musical scales and its relations",
5
5
  "keywords": [
6
6
  "scale",
@@ -16,12 +16,12 @@
16
16
  ],
17
17
  "types": "dist/index.d.ts",
18
18
  "dependencies": {
19
- "@tonaljs/chord-type": "^4.7.3",
20
- "@tonaljs/collection": "^4.7.3",
21
- "@tonaljs/core": "^4.7.3",
22
- "@tonaljs/note": "^4.7.3",
23
- "@tonaljs/pcset": "^4.7.3",
24
- "@tonaljs/scale-type": "^4.7.3"
19
+ "@tonaljs/chord-type": "^4.8.0",
20
+ "@tonaljs/collection": "^4.8.0",
21
+ "@tonaljs/core": "^4.8.0",
22
+ "@tonaljs/note": "^4.9.0",
23
+ "@tonaljs/pcset": "^4.8.0",
24
+ "@tonaljs/scale-type": "^4.8.0"
25
25
  },
26
26
  "author": "danigb@gmail.com",
27
27
  "license": "MIT",