Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Chord.getChord function #167

Merged
merged 5 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
]
},
"devDependencies": {
"@types/jest": "^24.0.23",
"@types/node": "^13.1.4",
"@types/jest": "^25.2.1",
"@types/node": "^13.13.4",
"husky": "^3.1.0",
"jest": "^24.8.0",
"jest-config": "^24.8.0",
"jest": "^25.4.0",
"jest-config": "^25.4.0",
"lerna": "^3.19.0",
"lint-staged": "^10.0.8",
"lodash": "^4.17.15",
Expand All @@ -74,7 +74,7 @@
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^5.1.2",
"rollup-plugin-typescript2": "^0.25.3",
"ts-jest": "^24.2.0",
"ts-jest": "^25.4.0",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
"typescript": "^3.7.3"
Expand Down
6 changes: 3 additions & 3 deletions packages/chord-type/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ describe("@tonaljs/chord-type", () => {
// sorted
expect(ChordType.names().slice(0, 5)).toEqual([
"fifth",
"suspended 4th",
"suspended 4th seventh",
"suspended fourth",
"suspended fourth seventh",
"augmented",
"major seventh b6"
"major seventh flat sixth"
]);
});

Expand Down
51 changes: 39 additions & 12 deletions packages/chord/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,54 @@ const { Chord } = require("@tonaljs/tonal");

## API

#### `Chord.get(name: string) => Scale`
#### `Chord.getChord(type: string, tonic?: string, root?: string) => Chord`

Get a chord from a chord name. Unlike `chordType`, `chord` accepts tonics in the chord name and returns the a Scale data object (a ChordType with extended properties):
Get the chord properties from a chord type and an (optional) tonic and (optional) root. Notice that the tonic must be present if the root is present. Also the root must be part of the chord notes.

- tonic: the tonic of the chord, or "" if not present
- notes: an array of notes, or empty array if tonic is not present
It returns the same object that `ChordType.get` but with additional properties:

- symbol: the chord symbol (a combiation of the tonic, chord type shortname and root, if present). For example: `Cmaj7`, `Db7b5/F`. The symbol always uses pitch classes (note names without octaves) for both the tonic and root.
- tonic: the tonic of the chord (or an empty string if not present)
- root: the root of the chord (or an empty string if not present)
- rootDegree: the degree of the root. 0 if root not present. A number greater than 0 if present, where 1 indicates the tonic, 2 the second note (normally the 3th), 2 the third note (normally the 5th), etc.
- notes: an array of notes, or empty array if tonic is not present. If the root is pres

Example:

```js
Chord.get("Cmaj7");
// =>
Chord.getChord("maj7", "G4", "B4"); // =>
// {
// name: "C major seventh",
// tonic: "C",
// empty: false,
// name: "G major seventh over B",
// symbol: "Gmaj7/B",
// tonic: "G4",
// root: "B4",
// rootDegree: 2,
// setNum: 2193,
// type: "major seventh",
// aliases: ["maj7", "Δ", "ma7", "M7", "Maj7"],
// chroma: "100010010001",
// intervals: ["1P", "3M", "5P", "7M"],
// notes: ["C", "E", "G", "B"],
// quality: "Major"
// };
// normalized: "100010010001",
// notes: ["G4", "B4", "D5", "F#5"],
// quality: "Major",
// }
```

#### `Chord.get(name: string) =>`

An alias of `Chord.getChord` but accepts a chord symbol as parameter.

```js
Chord.get("Cmaj7");
// same as
Chord.getChord("maj7", "C");
```

Important: currently chord with roots are NOT allowed (will be implemented in next version):

```js
Chord.get("Cmaj7/E"); // => { empty: true }
```

#### `Chord.detect(notes: string[]) => string[]`
Expand All @@ -52,7 +79,7 @@ Alias of [chord-detect](/packages/chord-detect)

#### `Chord.transpose(chordName: string, intervalName: string) => string`

Transpose a chord name by an interval:
Transpose a chord symbol by an interval:

```js
Chord.transpose("Eb7b9", "5P"); // => "Bb7b9"
Expand Down
75 changes: 67 additions & 8 deletions packages/chord/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import {

import {
deprecate,
distance,
note,
NoteName,
tokenizeNote,
transpose as transposeNote
} from "@tonaljs/core";

import { isSubsetOf, isSupersetOf, modes } from "@tonaljs/pcset";
import { isSubsetOf, isSupersetOf } from "@tonaljs/pcset";

import { all as scaleTypes } from "@tonaljs/scale-type";
export { detect } from "@tonaljs/chord-detect";
Expand All @@ -24,12 +25,18 @@ type ChordNameTokens = [string, string]; // [TONIC, SCALE TYPE]
interface Chord extends ChordType {
tonic: string | null;
type: string;
root: string;
rootDegree: number;
symbol: string;
notes: NoteName[];
}

const NoChord: Chord = {
empty: true,
name: "",
symbol: "",
root: "",
rootDegree: 0,
type: "",
tonic: null,
setNum: NaN,
Expand Down Expand Up @@ -85,17 +92,68 @@ export function tokenize(name: string): ChordNameTokens {
* Get a Chord from a chord name.
*/
export function get(src: ChordName | ChordNameTokens): Chord {
const { type, tonic } = findChord(src);
if (src === "") {
return NoChord;
}
if (Array.isArray(src) && src.length === 2) {
return getChord(src[1], src[0]);
} else {
const [tonic, type] = tokenize(src);
const chord = getChord(type, tonic);
return chord.empty ? getChord(src) : chord;
}
}

/**
* Get chord properties
*
* @param typeName - the chord type name
* @param [tonic] - Optional tonic
* @param [root] - Optional root (requires a tonic)
*/
export function getChord(
typeName: string,
optionalTonic?: string,
optionalRoot?: string
): Chord {
const type = getChordType(typeName);
const tonic = note(optionalTonic || "");
const root = note(optionalRoot || "");

if (
type.empty ||
(optionalTonic && tonic.empty) ||
(optionalRoot && root.empty)
) {
return NoChord;
}

if (!type || type.empty) {
const rootInterval = distance(tonic.pc, root.pc);
const rootDegree = type.intervals.indexOf(rootInterval) + 1;
if (!root.empty && !rootDegree) {
return NoChord;
}

const notes: string[] = tonic
? type.intervals.map(i => transposeNote(tonic, i))
: [];
const name = tonic ? tonic + " " + type.name : type.name;
return { ...type, name, type: type.name, tonic: tonic || "", notes };
const notes = tonic.empty
? []
: type.intervals.map(i => transposeNote(tonic, i));
typeName = type.aliases.indexOf(typeName) !== -1 ? typeName : type.aliases[0];
const symbol = `${tonic.empty ? "" : tonic.pc}${typeName}${
root.empty ? "" : "/" + root.pc
}`;
const name = `${optionalTonic ? tonic.pc + " " : ""}${type.name}${
optionalRoot ? " over " + root.pc : ""
}`;
return {
...type,
name,
symbol,
type: type.name,
root: root.name,
rootDegree,
tonic: tonic.name,
notes
};
}

export const chord = deprecate("Chord.chord", "Chord.get", get);
Expand Down Expand Up @@ -180,6 +238,7 @@ export function reduced(chordName: string): string[] {
}

export default {
getChord,
get,
detect,
chordScales,
Expand Down
69 changes: 55 additions & 14 deletions packages/chord/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,64 @@ describe("tonal-chord", () => {
expect(Chord.tokenize("C4")).toEqual(["C", "4"]);
});

describe("getChord", () => {
test("Chord properties", () => {
expect(Chord.getChord("maj7", "G4", "B4")).toEqual({
empty: false,
name: "G major seventh over B",
symbol: "Gmaj7/B",
tonic: "G4",
root: "B4",
rootDegree: 2,
setNum: 2193,
type: "major seventh",
aliases: ["maj7", "Δ", "ma7", "M7", "Maj7"],
chroma: "100010010001",
intervals: ["1P", "3M", "5P", "7M"],
normalized: "100010010001",
notes: ["G4", "B4", "D5", "F#5"],
quality: "Major"
});
});
test("without root", () => {
expect(Chord.getChord("M7", "G")).toMatchObject({
symbol: "GM7",
name: "G major seventh",
notes: ["G", "B", "D", "F#"]
});
});
test("rootDegrees", () => {
expect(Chord.getChord("maj7", "C", "C").rootDegree).toBe(1);
expect(Chord.getChord("maj7", "C", "D").empty).toBe(true);
});
test("without tonic nor root", () => {
expect(Chord.getChord("dim")).toEqual({
symbol: "dim",
name: "diminished",
tonic: "",
root: "",
rootDegree: 0,
type: "diminished",
aliases: ["dim", "°", "o"],
chroma: "100100100000",
empty: false,
intervals: ["1P", "3m", "5d"],
normalized: "100000100100",
notes: [],
quality: "Diminished",
setNum: 2336
});
});
});

test("chord", () => {
expect(Chord.get("Cmaj7")).toEqual({
empty: false,
symbol: "Cmaj7",
name: "C major seventh",
tonic: "C",
root: "",
rootDegree: 0,
setNum: 2193,
type: "major seventh",
aliases: ["maj7", "Δ", "ma7", "M7", "Maj7"],
Expand All @@ -37,23 +90,11 @@ describe("tonal-chord", () => {
});
expect(Chord.get("hello").empty).toBe(true);
expect(Chord.get("").empty).toBe(true);
expect(Chord.get("C")).toEqual(Chord.get("C major"));
expect(Chord.get("C").name).toEqual("C major");
});

test("chord without tonic", () => {
expect(Chord.get("dim")).toEqual({
aliases: ["dim", "°", "o"],
chroma: "100100100000",
empty: false,
intervals: ["1P", "3m", "5d"],
name: "diminished",
normalized: "100000100100",
notes: [],
quality: "Diminished",
setNum: 2336,
tonic: "",
type: "diminished"
});
expect(Chord.get("dim")).toMatchObject({ name: "diminished" });
expect(Chord.get("dim7")).toMatchObject({ name: "diminished seventh" });
expect(Chord.get("alt7")).toMatchObject({ name: "altered" });
});
Expand Down
2 changes: 1 addition & 1 deletion packages/tonal/browser/tonal.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/tonal/browser/tonal.min.js.map

Large diffs are not rendered by default.

Loading