12

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';
Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
Frantisek
  • 7,485
  • 15
  • 59
  • 102

8 Answers8

13

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

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • 1
    Accepting this answer for providing the full solution, including the bass part. – Frantisek Oct 29 '11 at 04:31
  • 1
    If you feel like expanding the function, do it so that it also understand flats and keys and everything you consider necessary and I'm willing to reward you with a 400 reputation bounty! If you do so, please keep in mind that I need it in the Northern European, which goes like this: C C# D D# E F F# G G# A B H C ... it's in the table at http://en.wikipedia.org/wiki/Note – Frantisek Oct 29 '11 at 05:10
  • 1
    +1. This handles the modulus better than mine, and the function allows you to be more general (even more than the two parts specified in the question). – Matthew Flaschen Oct 29 '11 at 05:14
  • 2
    I think a full implementation is way beyond what I have time for (even with the offer of 400 rep). When transposing a single chord you don't have enough information as to whether flats or sharps apply: you also need to know the key that the song will be in after it is transposed. (Though you could probably take a guess that _most_ of the time you want, e.g., Bb not A#.) – nnnnnn Oct 29 '11 at 05:21
4

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>
Forivin
  • 14,780
  • 27
  • 106
  • 199
3
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).

Matthew Flaschen
  • 278,309
  • 50
  • 514
  • 539
  • The -1 decrementing seems to cause problems, try going down multiple times here > http://inekafe.rimmer.sk/ – Frantisek Oct 29 '11 at 03:52
  • Seems like it cannot transpose down from C to the end of the cycle, B. It returns undefined. ``C`` -1 returns ``undefined`` – Frantisek Oct 29 '11 at 03:54
  • @RiMMER, try the modified version. – Matthew Flaschen Oct 29 '11 at 04:02
  • it seems to work now. So for the bass part, I just look for a slash using indexOf, then transpose the part, if found, right? – Frantisek Oct 29 '11 at 04:04
  • Thanks, I'll figure out the rest then, but maybe it should be contained here for the future visitors too. By the way - are you a musician? You seem to have understood the problem pretty easily. – Frantisek Oct 29 '11 at 04:08
  • Not anymore, but I played clarinet way back, and my family is pretty musical. So I'm familiar with the basic scale. You should ask a followup about the circle of fifths. :) Also, feel free to post an addition to this answer with the `/` part. – Matthew Flaschen Oct 29 '11 at 04:10
1

Ok, so I've thought about this a fair bit now, and I have a functional answer. It's in standard Western Scale tones (sorry Northern Europeans).

To truly transpose a chord you need 3 pieces of information: 1) the CHORD name, 2) the OLDKEY, and 3) the NEWKEY. It's not always enough to modulate by an AMOUNT (is UP-2 from the key of E an F# or a Gb?).

Basically you need to preserve two distances – the distance in pitch between the CHORD and the OLDKEY, and the distance in 'letters' between the CHORD and the OLDKEY – when mapping the CHORD to the NEWKEY.

function transposeChord(chord, oldKey, newKey) {}

To simplify(?) this, I've predefined every scale and every possible note (and some impossible ones) in relation to that scale root.

    var scales = {
    // scale_form = [1-7, #1-7, b1-7, *1-7, bb1-7]
    "CScale": ["C", "D", "E", "F", "G", "A", "B", "C#", "D#", "E#", "F#", "G#", "A#", "B#", "Cb", "Db", "Eb", "Fb", "Gb", "Ab", "Bb", "C*", "D*", "E*", "F*", "G*", "A*", "B*", "Cbb", "Dbb", "Ebb", "Fbb", "Gbb", "Abb", "Bbb"],
    "GScale": ["G", "A", "B", "C", "D", "E", "F#", "G#", "A#", "B#", "C#", "D#", "E#", "F*", "Gb", "Ab", "Bb", "Cb", "Db", "Eb", "F", "G*", "A*", "B*", "C*", "D*", "E*", "F#*", "Gbb", "Abb", "Bbb", "Cbb", "Dbb", "Ebb", "Fb"],
    "DScale": ["D", "E", "F#", "G", "A", "B", "C#", "D#", "E#", "F*", "G#", "A#", "B#", "C*", "Db", "Eb", "F", "Gb", "Ab", "Bb", "C", "D*", "E*", "F#*", "G*", "A*", "B*", "C#*", "Dbb", "Ebb", "Fb", "Gbb", "Abb", "Bbb", "Cb"],
    "AScale": ["A", "B", "C#", "D", "E", "F#", "G#", "A#", "B#", "C*", "D#", "E#", "F*", "G*", "Ab", "Bb", "C", "Db", "Eb", "F", "G", "A*", "B*", "C#*", "D*", "E*", "F#*", "G#*", "Abb", "Bbb", "Cb", "Dbb", "Ebb", "Fb", "Gb"],
    "EScale": ["E", "F#", "G#", "A", "B", "C#", "D#", "E#", "F*", "G*", "A#", "B#", "C*", "D*", "Eb", "F", "G", "Ab", "Bb", "C", "D", "E*", "F#*", "G#*", "A*", "B*", "C#*", "D#*", "Ebb", "Fb", "Gb", "Abb", "Bbb", "Cb", "Db"],
    "BScale": ["B", "C#", "D#", "E", "F#", "G#", "A#", "B#", "C*", "D*", "E#", "F*", "G*", "A*", "Bb", "C", "D", "Eb", "F", "G", "A", "B*", "C#*", "D#*", "E*", "F#*", "G#*", "A#*", "Bbb", "Cb", "Db", "Ebb", "Fb", "Gb", "Ab"],
    "F#Scale": ["F#", "G#", "A#", "B", "C#", "D#", "E#", "F*", "G*", "A*", "B#", "C*", "D*", "E*", "F", "G", "A", "Bb", "C", "D", "E", "F#*", "G#*", "A#*", "B*", "C#*", "D#*", "E#*", "Fb", "Gb", "Ab", "Bbb", "Cb", "Db", "Eb"],
    "C#Scale": ["C#", "D#", "E#", "F#", "G#", "A#", "B#", "C*", "D*", "E*", "F*", "G*", "A*", "B*", "C", "D", "E", "F", "G", "A", "B", "C#*", "D#*", "E#*", "F#*", "G#*", "A#*", "B#*", "Cb", "Db", "Eb", "Fb", "Gb", "Ab", "Bb"],
    "G#Scale": ["G#", "A#", "B#", "C#", "D#", "E#", "F*", "G*", "A*", "B*", "C*", "D*", "E*", "F#*", "G", "A", "B", "C", "D", "E", "F#", "G#*", "A#*", "B#*", "C#*", "D#*", "E#*", "F**", "Gb", "Ab", "Bb", "Cb", "Db", "Eb", "F"],
    "D#Scale": ["D#", "E#", "F*", "G#", "A#", "B#", "C*", "D*", "E*", "F#*", "G*", "A*", "B*", "C#*", "D", "E", "F#", "G", "A", "B", "C#", "D#*", "E#*", "F**", "G#*", "A#*", "B#*", "C**", "Db", "Eb", "F", "Gb", "Ab", "Bb", "C"],
    "A#Scale": ["A#", "B#", "C*", "D#", "E#", "F*", "G*", "A*", "B*", "C#*", "D*", "E*", "F#*", "G#*", "A", "B", "C#", "D", "E", "F#", "G#", "A#*", "B#*", "C**", "D#*", "E#*", "F**", "G**", "Ab", "Bb", "C", "D#", "Eb", "F", "G"],
    // E#Scale:
    // B#Scale: 
    "FScale": ["F", "G", "A", "Bb", "C", "D", "E", "F#", "G#", "A#", "B", "C#", "D#", "E#", "Fb", "Gb", "Ab", "Bbb", "Cb", "Db", "Eb", "F*", "G*", "A*", "B#", "C*", "D*", "E*", "Fbb", "Gbb", "Abb", "Bbbb", "Cbb", ,"Dbb", ,"Ebb"],
    "BbScale": ["Bb", "C", "D", "Eb", "F", "G", "A", "B", "C#", "D#", "E", "F#", "G#", "A#", "Bbb", "Cb", "Db", "Ebb", "Fb", "Gb", "Ab", "B#", "C*", "D*", "E#", "F*", "G*", "A*", "Bbbb", "Cbb", "Dbb", "Ebbb", "Fbb", "Gbb", "Abb"],
    "EbScale": ["Eb", "F", "G", "Ab", "Bb", "C", "D", "E", "F#", "G#", "A", "B", "C#", "D#", "Ebb", "Fb", "Gb", "Abb", "Bbb", "Cb", "Db", "E#", "F*", "G*", "A#", "B#", "C*", "D*", "Ebbb", "Fbb", "Gbb", "Abbb", "Bbbb", "Cbb", "Dbb"],
    "AbScale": ["Ab", "Bb", "C", "Db", "Eb", "F", "G", "A", "B", "C#", "D", "E", "F#", "G#", "Abb", "Bbb", "Cb", "Dbb", "Ebb", "Fb", "Gb", "A#", "B#", "C*", "D#", "E#", "F*", "G*", "Abbb", "Bbbb", "Cbb", "Dbbb", "Ebbb", "Fbb", "Gbb"],
    "DbScale": ["Db", "Eb", "F", "Gb", "Ab", "Bb", "C", "D", "E", "F#", "G", "A", "B", "C#", "Dbb", "Ebb", "Fb", "Gbb", "Abb", "Bbb", "Cb", "D#", "E#", "F*", "G#", "A#", "B#", "C*", "Dbbb", "Ebbb", "Fbb", "Gbbb", "Abbb", "Bbbb", "Cbb"],
    "GbScale": ["Gb", "Ab", "Bb", "Cb", "Db", "Eb", "F", "G", "A", "B", "C", "D", "E", "F#", "Gbb", "Abb", "Bbb", "Cbb", "Dbb", "Ebb", "Fb", "G#", "A#", "B#", "C#", "D#", "E#", "F*", "Gbbb", "Abbb", "Bbbb", "Cbbb", "Dbbb", "Ebbb", "Fbb"]
    // CbScale:
    // FbScale:
    // BbbFlatScale:
    //  ...
    }       

Then you assign your scales based on the OLDKEY and NEWKEY:

var oldKeyScale = scales[key + "Scale"]
var newKeyScale = scales[newKey + "Scale"]

Finally, some regex to find and replace all those chord-roots/flats/sharps/doubleflats/etc with their corresponding position in the NEWKEY scale.

var transposedChord
transposedChord = chord.replace(/(([CDEFGAB]#\*)|([CDEFGAB]#)|([CDEFGAB]b+)|([CDEFGAB]\**))/g, function(match) {
    var i = oldKeyScale.indexOf(match)
    return newKeyScale[i]
})
return transposedChord

There's definitely a better, more 'think-like-a-computer' way to do this, but this will take

transposeChord("Am7/G", "C", "A#")

and return

"F*m7/E#"
Benny G
  • 11
  • 2
  • Why is all this specificity necessary? Because the progression **A | E | F#m | D** when taken up a halfstep to the key of A# (because some people are masochists) _should_ come out to **A# | E# | F*m | D** and any implementation that doesn't deal with double-sharps will give you **Gm** instead. – Benny G Feb 08 '17 at 20:34
  • In your comment you say that D would still be D after transposition. (Typo?) – nnnnnn Feb 08 '17 at 23:40
  • Very much a typo, good catch. Should be D# (and is when transposed via above method). – Benny G Feb 10 '17 at 01:16
  • You could avoid the switch statements if you stored the scale arrays as properties of an object with the key names ("C#", etc.) as property names. – nnnnnn Feb 10 '17 at 01:19
  • Haha had that exact thought yesterday. Initially i was getting some weird behavior with naming variables with a '#', but object keys should be fine. Worstcase i sub 'sharp' for '#'. Goodcall. – Benny G Feb 11 '17 at 19:20
1

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));
Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
1

Define your keys with an object:

var keys = ["A", "A#", "B", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#"];

Parse your chord with a regular expression:

var matches = /([A-G]#?)([^\/]*)(?:\/([A-G]#?))?/.exec(chord);
var key = matches[1];
var descriptor = matches[2];
var bass = matches[3];

Do a little math to get the new key:

var newKey = keys[(keys.indexOf(key) + transpose) % keys.length];
var newBass = keys[(keys.indexOf(bass) + transpose) % keys.length];

Put it all back together again:

var newChord = newKey + descriptor;
if (newBass) {
    newChord += "/" + newBass;
}
return newChord;
gilly3
  • 87,962
  • 25
  • 144
  • 176
  • There seems to be a problem with the expression: Invalid regular expression: /([A-G]#?)([^\/]*)(?:\/([A-G]#?)/: Unterminated group – Frantisek Oct 29 '11 at 04:02
  • I fixed it, but I'm unable to test it right now because I'm posting from my phone. – gilly3 Oct 29 '11 at 04:13
0
function transposechord(chord, amount){
   var scale = ["C","Cb","C#","D","Db","D#","E","Eb","E#","F","Fb","F#","G","Gb","G#",
         "A","Ab","A#","B","Bb","B#"];
   var transp = ["Cb","C","C#","Bb","Cb","C","C","C#","D","Db","D","D#","C","Db","D",
                 "D","D#","E","Eb","E","F","D","Eb","E", "E","E#","F#", "E","F","F#",
                 "Eb","Fb","F","F","F#","G","Gb","G","G#","F","Gb","G", "G","G#","A", 
                 "Ab","A","A#","G","Ab","A","A","A#","B","Bb","B","C","A","Bb","B", 
                 "B","B#","C#"];
   var subst = chord.match(/[^b#][#b]?/g);
   for(var ax in subst){
      if(scale.indexOf(subst[ax])!==-1){
         if(amount>0){
            for(ix=0;ix<amount;ix++){
                var pos = scale.indexOf(subst[ax]);
                var transpos = 3*pos-2+3;
                subst[ax] = transp[transpos+1];
            }
         }
         if(amount<0){
            for(ix=0;ix>amount;ix--){
                var pos = scale.indexOf(subst[ax]);
                var transpos = 3*pos-2+3;
                subst[ax] = transp[transpos-1];
                }
            }
       } 
   }
   chord=subst.join("");
}

chord = C/B, amount = 1: C#/C or chord = Gm7, amount 2: Am7

  • It would be nice to add some context and explanation to your code. Just posting a piece of code is not always helpful. – Meryovi Sep 25 '13 at 03:14
  • If you took C/B up one semitone it would "normally" be either C#/B# or Db/C (depending on what key the song is in). Not that my answer does that properly either. – nnnnnn Feb 08 '17 at 23:38
0

I needed a transpose for chord lines in a guitar/ukulele song sheet and came up with the following functions which maintain spacing if space allows, and re-uses 7/dim/sus4 type information:

    function transposeChord( chord, amount ) {
        const sharpnotes = ["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"];
        const flatnotes  = ["A","Bb","B","C","Db","D","Eb","E","F","Gb","G","Ab"];
        let rootChord = chord[0];
        if(chord[1] === '#' || chord[1] == 'b') {
            rootChord += chord[1];
        }
        amount = (amount % sharpnotes.length) || 1;
        if(amount < 0) { amount += sharpnotes.length; }
        for(let note=0; note < sharpnotes.length; ++note) {
            if(rootChord === sharpnotes[note]) {
                return( (sharpnotes[(note + amount) % sharpnotes.length]) + chord.substr(rootChord.length) );
            }
            if(rootChord === flatnotes[note]) {
                return( (flatnotes[(note + amount) % flatnotes.length]) + chord.substr(rootChord.length) );
            }
        }
        return ('???');
    }

    function transposeChordLine( line, amount ) {
        amount = amount || 1;
        let count = 0;
        let newLine = '';

        while(count < line.length) {
            if(line[count] >= 'A' && line[count] <= 'G') {
                let chord = line[count++];
                while (count < line.length && line[count] !== ' ' && (line[count] < 'A' || line[count] > 'G')) {
                    chord += line[count++];
                }
                let newChord = transposeChord(chord, amount);
                if(newChord.length < chord.length) {    // pad if shorter
                    newChord += " ";
                }
                if(newChord.length > chord.length && count < line.length && (line[count] < 'A' || line[count] > 'G')) { // trim if there's space
                    count++;
                }
                newLine += newChord;
            } else {
                newLine += line[count++];
            }
        }
        return(newLine);
    }

so (for example)

transposeChordLine("     C          D7    Dm7    Gb7 ", 4)

outputs

"     E          F#7   F#m7   Bb7 "

Flats and sharps are presumed to remain b/# when transposed if appropriate.

kevstev01
  • 330
  • 5
  • 13