How do I transpose music chords using JavaScript?

前端 未结 7 1361
忘掉有多难
忘掉有多难 2021-02-08 08:56

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 i

相关标签:
7条回答
  • 2021-02-08 09:28

    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#"
    
    0 讨论(0)
  • 2021-02-08 09:34

    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));
    
    0 讨论(0)
  • 2021-02-08 09:37

    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;
    
    0 讨论(0)
  • 2021-02-08 09:41
    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).

    0 讨论(0)
  • 2021-02-08 09:41
    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

    0 讨论(0)
  • 2021-02-08 09:47

    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>

    0 讨论(0)
提交回复
热议问题