Skip to content

Commit

Permalink
Note.enharmonic calculates octaves of an equivalent pitch class (#225)
Browse files Browse the repository at this point in the history
  • Loading branch information
danigb authored Nov 13, 2020
1 parent 9733196 commit 8c3a97c
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 19 deletions.
25 changes: 21 additions & 4 deletions packages/note/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,15 @@ Note.transposeFifths("G", 3); // => "E"

#### `Note.distance(from: string, to: string) => string`

Find interval between notes:
Find the interval between two notes:

```js
Note.distance("C", "D").toEqual("2M");
Note.distance("C3", "E3").toEqual("3M");
Note.distance("C3", "E4").toEqual("10M");
```

### Names collections
### Named collections

#### `names(array?: any[]) => string[]`

Expand Down Expand Up @@ -185,12 +188,26 @@ Note.simplify("C##"); // => "D"
Note.simplify("C###"); // => "D#"
```

#### `enharmonic(noteName: string) => string`
#### `enharmonic(noteName: string, pitchClass?: string) => string`

Given a note name, returns it enharmonic not (or "" if not valid note):
Given a note name, returns its enharmonic (or "" if not valid note):

```js
Note.enharmonic("C#"); // => "Db"
Note.enharmonic("C##"); // => "D"
Note.enharmonic("C###"); // => "Eb"
```

The destination pitch class can be enforced to calculate the octave:

```js
Note.enharmonic("F2", "E#"); // => "E#2"
Note.enharmonic("B2", "Cb"); // => "Cb3"
Note.enharmonic("C2", "B#"); // => "B#1"
```

Enforced pitch class must have the same chroma as the note, otherwise "" is returned:

```js
Note.enharmonic("F2", "Eb"); // => ""
```
63 changes: 49 additions & 14 deletions packages/note/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,30 +216,65 @@ export function sortedUniqNames(notes: any[]): string[] {
* simplify("C###")
* simplify("B#4") // => "C5"
*/
export const simplify = nameBuilder(true);

export const simplify = (noteName: NoteName | Pitch): string => {
const note = get(noteName);
if (note.empty) {
return "";
}
return midiToNoteName(note.midi || note.chroma, {
sharps: note.alt > 0,
pitchClass: note.midi === null,
});
};
/**
* Get enharmonic of a note
*
* @function
* @param {string} note
* @return {string} the enharmonic note or '' if not valid note
* @param [string] - [optional] Destination pitch class
* @return {string} the enharmonic note name or '' if not valid note
* @example
* Note.enharmonic("Db") // => "C#"
* Note.enharmonic("C") // => "C"
* Note.enharmonic("F2","E#") // => "E#2"
*/
export const enharmonic = nameBuilder(false);
export function enharmonic(noteName: string, destName?: string) {
const src = get(noteName);
if (src.empty) {
return "";
}

// destination: use given or generate one
const dest = get(
destName ||
midiToNoteName(src.midi || src.chroma, {
sharps: src.alt < 0,
pitchClass: true,
})
);

// ensure destination is valid
if (dest.empty || dest.chroma !== src.chroma) {
return "";
}

// if src has no octave, no need to calculate anything else
if (src.oct === undefined) {
return dest.pc;
}

function nameBuilder(sameAccidentals: boolean) {
return (noteName: NoteName | Pitch): string => {
const note = get(noteName);
if (note.empty) {
return "";
}
const sharps = sameAccidentals ? note.alt > 0 : note.alt < 0;
const pitchClass = note.midi === null;
return midiToNoteName(note.midi || note.chroma, { sharps, pitchClass });
};
// detect any octave overflow
const srcChroma = src.chroma - src.alt;
const destChroma = dest.chroma - dest.alt;
const destOctOffset =
srcChroma > 11 || destChroma < 0
? -1
: srcChroma < 0 || destChroma > 11
? +1
: 0;
// calculate the new octave
const destOct = src.oct + destOctOffset;
return dest.pc + destOct;
}

export default {
Expand Down
8 changes: 7 additions & 1 deletion packages/note/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,14 @@ describe("note", () => {
expect(Note.enharmonic("C###")).toEqual("Eb");
expect(Note.enharmonic("B#4")).toEqual("C5");
const notes = $("C## C### F##4 Gbbb5 B#4 Cbb4");
expect(notes.map(Note.enharmonic)).toEqual($("D Eb G4 E5 C5 A#3"));
expect(notes.map((n) => Note.enharmonic(n))).toEqual(
$("D Eb G4 E5 C5 A#3")
);
expect(Note.enharmonic("x")).toEqual("");
expect(Note.enharmonic("F2", "E#")).toBe("E#2");
expect(Note.enharmonic("B2", "Cb")).toBe("Cb3");
expect(Note.enharmonic("C2", "B#")).toBe("B#1");
expect(Note.enharmonic("F2", "Eb")).toBe("");
});

test("transposeFifths", () => {
Expand Down

0 comments on commit 8c3a97c

Please sign in to comment.