diff --git a/packages/note/README.md b/packages/note/README.md index 461dcecb..3de79db9 100644 --- a/packages/note/README.md +++ b/packages/note/README.md @@ -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[]` @@ -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"); // => "" +``` diff --git a/packages/note/index.ts b/packages/note/index.ts index d0b6b366..e344f8e8 100644 --- a/packages/note/index.ts +++ b/packages/note/index.ts @@ -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 { diff --git a/packages/note/test.ts b/packages/note/test.ts index 668b57d0..76092ea4 100644 --- a/packages/note/test.ts +++ b/packages/note/test.ts @@ -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", () => {