Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I transpose music chords using JavaScript?

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';
like image 229
Frantisek Avatar asked Oct 29 '11 03:10

Frantisek


2 Answers

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));
like image 165
Steven Spungin Avatar answered Nov 17 '22 02:11

Steven Spungin


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

like image 32
nnnnnn Avatar answered Nov 17 '22 04:11

nnnnnn