Skip to content

Instantly share code, notes, and snippets.

@corlaez
Last active January 18, 2021 04:53
Show Gist options
  • Save corlaez/101da1a872ad348c4f0e071a4ba491fa to your computer and use it in GitHub Desktop.
Save corlaez/101da1a872ad348c4f0e071a4ba491fa to your computer and use it in GitHub Desktop.
Transpose chords function
function transposeChord(inputChord, halfStepsToTranspose) {
const splittedChord = inputChord.split("/");
if (splittedChord.length > 2) throw new Error("Format error, too many slashes.");
const [transposedChordBase, modifier] = transposeChordBase(splittedChord, halfStepsToTranspose);
const maybeTransposedBass = transposeBass(splittedChord, halfStepsToTranspose);
const transposedChordHasAccident =
transposedChordBase.length > 1 &&
["#", "b"].includes(transposedChordBase.charAt(1));
if (transposedChordHasAccident) {
return (
transposedChordBase +
modifier +
maybeTransposedBass.map(tb => "/" + tb) +
" or " +
switchAccident(transposedChordBase) +
modifier +
maybeTransposedBass.map(switchAccident).map(tb => "/" + tb)
);
} else {
return transposedChordBase + modifier + maybeTransposedBass.map(tb => "/" + tb);
}
}
// Helpers
function transposeChordBase(splittedChord, halfStepsToTranspose) {
const chord = splittedChord[0];
const chordHasAccident = ["#", "b"].includes(chord.charAt(1));
const modifierIndex = chordHasAccident ? 2 : 1;
const chordBase = chord.substring(0, modifierIndex);
const modifier = chord.substring(modifierIndex, chord.length);
const transposedChordBase = sumHalfSteps(chordBase, halfStepsToTranspose);
return [transposedChordBase, modifier];
}
function transposeBass(splittedChord, halfStepsToTranspose) {
let maybeTransposedBass = new MaybeEmptyString();
if (splittedChord.length == 2) {
const bass = splittedChord[1]
maybeTransposedBass = maybeTransposedBass.withValue(
sumHalfSteps(bass, halfStepsToTranspose)
);
}
return maybeTransposedBass
}
const notes = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"];
class MaybeEmptyString {
constructor(value) {
this.defaultValue = "";
this.value = value;
}
withValue = (value) => {
return new MaybeEmptyString(value);
};
map = (fn) => {
if (this.value == null) return this;
else return this.withValue(fn(this.value));
};
toString = () => {
if (this.value == null) return this.defaultValue;
else return this.value;
};
}
function switchAccident(note) {
const bemols = {
Bb: "A#",
Db: "C#",
Eb: "D#",
Gb: "F#",
Ab: "G#",
"A#": "Bb",
"C#": "Db",
"D#": "Eb",
"F#": "Gb",
"G#": "Ab",
};
return bemols[note];
}
function sumHalfStepsForChord(chord, n) {
const hasAccident = ["#", "b"].includes(chord.charAt(1));
const modifierIndex = hasAccident ? 2 : 1;
const note = chord.substring(0, modifierIndex);
const modifier = chord.substring(modifierIndex, chord.length);
return sumHalfSteps(note, n) + modifier;
}
function sumHalfSteps(note, n) {
const noteIndex = getNoteIndex(note);
let newIndex = noteIndex + n;
newIndex = newIndex % 12;
if (newIndex < 0) {
newIndex = 12 + newIndex;
}
return notes[newIndex];
}
function getNoteIndex(note) {
if (note.length > 1) {
const justTheNote = note.substring(0, 1);
const noteWithAccident = note.substring(0, 2);
const accident = note.charAt(1);
if (accident == "#") return notes.indexOf(noteWithAccident);
else if (accident == "b")
return notes.indexOf(switchAccident(noteWithAccident));
else return notes.indexOf(justTheNote);
} else {
return notes.indexOf(note);
}
}
// test
const chords = [
"F",
"F#",
"Gb",
"A/C#",
"F#m/C#",
"F#m7/C#",
"F#maj7/C#",
"F#7/C#",
"F#sus2/C#",
"F#sus7/C#",
"Fm/C",
"Fm7/C",
"Fmaj7/C",
"F7/C",
"Fsus2/C",
"Fsus7/C",
];
/* Results:
G# or Ab
A
A
C/E
Am/E
Am7/E
Amaj7/E
A7/E
Asus2/E
Asus7/E
G#m/D# or Abm/Eb
G#m7/D# or Abm7/Eb
G#maj7/D# or Abmaj7/Eb
G#7/D# or Ab7/Eb
G#sus2/D# or Absus2/Eb
G#sus7/D# or Absus7/Eb
*/
chords.forEach((c) =>
console.log(transposeChord(c, 3))
);
/* Known limitations:
1. It does not check chord modifiers, any string is valid.
2. It does not understand chord modifiers, any bass note is valid as long as it is a note.
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment