How do I transpose music chords using JavaScript?

function transpose(chord, increment)
{
    var cycle = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
    var el = chord.charAt(0);
    if(chord.length > 1 && chord.charAt(1) == '#')
    {
        el += "#";   
    }
    var ind = cycle.indexOf(el);
    var newInd = (ind + increment + cycle.length) % cycle.length;
    var newChord = cycle[newInd];
    return newChord + chord.substring(el.length);
}

I'll let you figure out the bass part, since it's really just calling the function twice.

Also, you can add the code here before the function for old browsers that don't support indexOf.

I put a demo on jsFiddle.

EDIT: The issue was with negative modulus. The above will work as long as long as the negative isn't more than the length (e.g. you can't transpose 100 steps down).


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#".


Just to expand on nnnnnn's answer. We can take his code and add a little bit more code to actually make it work with flats.

transposeChord("F#sus7/A#", 1)
> "Gsus7/B"

transposeChord("Bb", 1)
> "B"

... works like a charm

function transposeChord(chord, amount) {
    var scale = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
    var normalizeMap = {"Cb":"B", "Db":"C#", "Eb":"D#", "Fb":"E", "Gb":"F#", "Ab":"G#", "Bb":"A#",  "E#":"F", "B#":"C"}
    return chord.replace(/[CDEFGAB](b|#)?/g, function(match) {
        var i = (scale.indexOf((normalizeMap[match] ? normalizeMap[match] : match)) + amount) % scale.length;
        return scale[ i < 0 ? i + scale.length : i ];
    })
}
<!-- Example Page -->
Chord:        <input id="chord" type="text" value="C#" style="width:70px"> 
transposed by <input id="amount" type="number" value="0" style="width:30px"> 
=             <input id="new-chord" type="text" style="width:70px">
              <button onclick="document.getElementById('new-chord').value = transposeChord(document.getElementById('chord').value,parseInt(document.getElementById('amount').value))">Calculate</button>