I was wondering how would one create a javascript function for transposing music chords.
Since I don't expect everyone to be a musician here, I'll try to explain how it works in music theory. I hope I don't forget something. If yes, musicians, please, correct me.
1) The simple chords
The simple chords are almost as simple as an alphabet and it goes like this:
C, C#, D, D#, E, F, F#, G, G#, A, A# B
From B it loops all over again to C. Therefore, If the original chord is E
and we want to transpose +1, the resulting chord is F
. If we transpose +4, the resulting chord is G#
.
2) Expanded chords
They work almost like the simple chords, but contain a few more characters, which can safely be ignored when transposing. For example:
Cmi, C#7, Dsus7, Emi, Fsus4, F#mi, G ...
So again, as with the simple chords, if we transpose Dsus7
+ 3 = Fsus7
3) Non-root bass tone
A problem arises when the bass plays a different tone than the chord root tone. This is marked by a slash after the chord and also needs to be transposed. Examples:
C/G, Dmi/A, F#sus7/A#
As with examples 1 and 2, everything is the same, but the part after the slash needs transpose too, therefore:
C/G
+ 5 = F/C
F#sus7/A#
+ 1 = Gsus7/B
I think this should be all, unless I forgot something.
So basically, imagine you have a javascript variable called chord
and the transpose value transpose
. What code would transpose the chord?
Example:
var chord = 'F#sus7/C#';
var transpose = 3; // remember this value also may be negative, like "-4"
... code here ...
var result; // expected result = 'Asus7/E';
All of these solutions are lacking the fact that after transposing a note, it needs to be converted to a sharp or flat depending on the key or chord.
So the API must be:
transpose(note, semitones, useSharps)
Here is my implementation. It also handles multiple # and b modifiers.
function transposeNote(note, semitones, useSharps) {
// parse root followed by modifiers (# and b)
const rx = /^([a-gA-G])([#b]*)$/;
const m = rx.exec(note);
if (!m) {
return null;
}
// convert note from 0 to 11 based off of A
let root;
switch (m[1].toUpperCase()) {
case "A":
root = 0;
break;
case "B":
root = 2;
break;
case "C":
root = 3;
break;
case "D":
root = 5;
break;
case "E":
root = 7;
break;
case "F":
root = 8;
break;
case "G":
root = 10;
break;
}
// modify root
let mods = m[2];
if (mods) {
for (var i = 0; i < mods.length; i++) {
if (mods.charAt(i) === "#") {
root++;
} else {
root--;
}
}
}
// transpose note
root = (root + semitones) % 12;
if (root < 0) {
root += 12
}
// convert back to a note
const sharps = [
"A",
"A#",
"B",
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#"
];
const flats = [
"A",
"Bb",
"B",
"C",
"Db",
"D",
"Eb",
"E",
"F",
"Gb",
"G",
"Ab"
];
const transposedNote = useSharps ? sharps[root] : flats[root];
return transposedNote;
}
function transposeChord(chord, semitones, useSharps) {
const rx = /^([a-gA-G][#b]*)\s*([^\/]*)(\/[a-gA-G][#b]*)?$/;
const m = rx.exec(chord);
if (!m) {
return null;
}
const root = transposeNote(m[1], semitones, useSharps);
const quality = m[2] || "";
let bass = m[3] || "";
if (bass.length > 0) {
bass = "/" + transposeNote(bass.substring(1), semitones, useSharps);
}
return root + quality + bass;
}
Use it like this
console.log(transposeChord("Cmin7/Eb", 3, false));
How about a little somethin' like this:
function transposeChord(chord, amount) {
var scale = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
return chord.replace(/[CDEFGAB]#?/g,
function(match) {
var i = (scale.indexOf(match) + amount) % scale.length;
return scale[ i < 0 ? i + scale.length : i ];
});
}
alert(transposeChord("Dm7/G", 2)); // gives "Em7/A"
alert(transposeChord("Fmaj9#11", -23)); // gives "F#maj9#11"
Note that I threw in the "F#maj9#11" example just to give you more to think about with regard to what makes up a valid chord name: you may find a "#" sharp symbol that doesn't follow a letter (in this case it belongs to the "11").
And, obviously, my function only understands sharps, not flats, and doesn't understand keys, so, e.g., transposeChord("C/E", 1)
will give "C#/F" when it really should be "C#/E#".
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With