Last active
January 18, 2021 04:53
-
-
Save corlaez/101da1a872ad348c4f0e071a4ba491fa to your computer and use it in GitHub Desktop.
Transpose chords function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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