【问题标题】:How do I transpose music chords using JavaScript?如何使用 JavaScript 转置音乐和弦?
【发布时间】:2011-12-17 17:18:27
【问题描述】:

我想知道如何创建一个用于转置音乐和弦的 javascript 函数。

由于我不希望这里的每个人都成为音乐家,所以我将尝试解释它在音乐理论中是如何运作的。我希望我不会忘记什么。如果是,音乐家,请纠正我。

1) 简单的和弦

简单的和弦几乎和字母一样简单,它是这样的:

C、C#、D、D#、E、F、F#、G、G#、A、A# B

它从 B 循环到 C。因此,如果原始和弦是 E 并且我们想要转置 +1,则得到的和弦是 F。如果我们转置+4,得到的和弦是G#

2) 扩展和弦

它们的工作方式几乎与简单的和弦相似,但包含更多字符,在移调时可以安全地忽略这些字符。例如:

Cmi、C#7、Dsus7、Emi、Fsus4、F#mi、G ...

同样,和简单的和弦一样,如果我们转置Dsus7 + 3 = Fsus7

3) 非根低音调

当贝司演奏的音调与和弦根音不同时,就会出现问题。这在和弦后用斜线标记,也需要移调。例子:

C/G、Dmi/A、F#sus7/A#

和例子1和2一样,一切都是一样的,但斜线后面的部分也需要转置,因此:

C/G + 5 = F/C

F#sus7/A# + 1 = Gsus7/B

我想这应该就是全部了,除非我忘记了什么。

所以基本上,假设您有一个名为chord 的javascript 变量和转置值transpose。什么代码可以转调和弦?

例子:

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';

【问题讨论】:

    标签: javascript music-notation


    【解决方案1】:

    像这样的小东西怎么样:

    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"
    

    请注意,我加入“F#maj9#11”示例只是为了让您更多地思考构成有效和弦名称的内容:您可能会发现不跟随的“#”尖符号一个字母(在这种情况下它属于“11”)。

    而且,很明显,我的函数只能理解升号,而不是降号,并且不理解键,因此,例如,transposeChord("C/E", 1) 将在它真正应该是“C#/E#”时给出“C#/F”。

    【讨论】:

    • 接受这个答案是为了提供完整的解决方案,包括低音部分。
    • 如果你想扩展这个功能,就让它也能理解单位和钥匙以及你认为必要的一切,我愿意用 400 声望奖励你!如果你这样做,请记住我在北欧需要它,它是这样的:C C# D D# E F F# G G# A B H C ...它在en.wikipedia.org/wiki/Note的表格中@
    • +1。这比我的处理模数更好,并且该功能使您更通用(甚至超过问题中指定的两个部分)。
    • 我认为完整的实现远远超出了我的时间(即使提供 400 个代表)。移调单个和弦时,您没有足够的信息来判断是否适用降号或升号:您还需要知道歌曲在移调后的调性。 (尽管您可能会猜测大部分是您想要的,例如,Bb 而不是 A#。)
    【解决方案2】:

    只是为了扩展 nnnnnn 的答案。我们可以使用他的代码并添加更多代码以使其真正适用于单位。

    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>

    【讨论】:

      【解决方案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);
      }
      

      我会让你弄清楚低音部分,因为它实际上只是调用了两次函数。

      此外,对于不支持indexOf的旧浏览器,您可以在函数前添加代码here

      我在 jsFiddle 上放了一个demo

      编辑:问题出在负模数上。只要负数不超过长度(例如,您不能向下移 100 步),上述方法就可以工作。

      【讨论】:

      • -1 递减似乎会导致问题,这里尝试多次下降> inekafe.rimmer.sk
      • 似乎它不能从 C 向下转置到循环结束,B。它返回未定义。 C -1 返回undefined
      • @RiMMER,试试修改后的版本。
      • 它现在似乎可以工作了。所以对于低音部分,我只是使用 indexOf 查找斜线,然后转置该部分,如果找到,对吧?
      • 谢谢,我会弄清楚其余的,但也许它也应该包含在这里以供未来的访问者使用。顺便说一句 - 你是音乐家吗?你似乎很容易理解这个问题。
      【解决方案4】:

      用一个对象定义你的键:

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

      用正则表达式解析你的和弦:

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

      做一些数学运算以获得新密钥:

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

      将它们重新组合在一起:

      var newChord = newKey + descriptor;
      if (newBass) {
          newChord += "/" + newBass;
      }
      return newChord;
      

      【讨论】:

      • 表达式似乎有问题: Invalid regular expression: /([AG]#?)([^\/]*)(?:\/([AG]#?) /: 未终止组
      • 我修复了它,但我现在无法测试它,因为我是用手机发帖的。
      【解决方案5】:

      好的,所以我现在已经考虑过这个问题,并且我有一个实用的答案。它是标准的西方音阶音调(对不起北欧人)。

      要真正转调一个和弦,您需要 3 条信息:1) CHORD 名称,2) OLDKEY,和 3) NEWKEY。仅以 AMOUNT 调制并不总是足够的(从 E 调的 UP-2 是 F# 还是 Gb?)。

      基本上,在将 CHORD 映射到 NEWKEY 时,您需要保留两个距离——CHORD 和 OLDKEY 之间的音高距离,以及 CHORD 和 OLDKEY 之间的“字母”距离。

      function transposeChord(chord, oldKey, newKey) {}
      

      为了简化(?)这一点,我已经预定义了与该音阶根相关的每个音阶和每个可能的音符(以及一些不可能的音符)。

          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:
          //  ...
          }       
      

      然后您根据 OLDKEY 和 NEWKEY 分配您的音阶:

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

      最后,一些正则表达式可以找到所有这些和弦根音/降音/升音/双降音/等,并将它们替换为它们在 NEWKEY 音阶中的相应位置。

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

      肯定有更好、更“像计算机一样思考”的方式来做到这一点,但这需要

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

      然后返回

      "F*m7/E#"
      

      【讨论】:

      • 为什么所有这些特殊性都是必要的?因为级数A | E | F#米 | D当向A#调半步时(因为有些人是自虐狂)应该出来A# | E# | F*米 | D 和任何不处理双升号的实现都会给你Gm
      • 在您的评论中,您说 D 在转置后仍然是 D。 (错字?)
      • 非常错误,很好。应该是 D#(并且是通过上述方法转置时)。
      • 如果将比例数组存储为对象的属性,并将键名(“C#”等)作为属性名,则可以避免使用 switch 语句。
      • 哈哈昨天也有这个想法。最初,我在用“#”命名变量时遇到了一些奇怪的行为,但对象键应该没问题。最坏情况下,我将 '#' 替换为 'sharp'。好电话。
      【解决方案6】:

      所有这些解决方案都缺少这样一个事实,即在移调音符后,需要根据调或和弦将其转换为升号或降号。

      所以 API 必须是:

      transpose(note, semitones, useSharps)
      

      这是我的实现。它还处理多个 # 和 b 修饰符。

      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;
      }
      

      这样使用

      console.log(transposeChord("Cmin7/Eb", 3, false));
      

      【讨论】:

        【解决方案7】:
        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("");
        }
        

        和弦 = C/B,数量 = 1: C#/C 要么 和弦 = Gm7,数量 2: Am7

        【讨论】:

        • 在您的代码中添加一些上下文和解释会很好。仅仅发布一段代码并不总是有帮助的。
        • 如果您将 C/B 提高一个半音,“通常”将是 C#/B# 或 Db/C(取决于歌曲所在的键)。并不是说我的回答也正确。
        【解决方案8】:

        我需要为吉他/尤克里里乐谱中的和弦线进行转置,并提出了以下功能,如果空间允许,可以保持间距,并重复使用 7/dim/sus4 类型信息:

            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);
            }
        

        所以(例如)

        transposeChordLine("     C          D7    Dm7    Gb7 ", 4)
        

        输出

        "     E          F#7   F#m7   Bb7 "
        

        如果适当,降号和升号在移调时假定保持 b/#。

        【讨论】:

          猜你喜欢
          • 2016-10-05
          • 2012-07-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-08-22
          • 2012-06-29
          • 1970-01-01
          相关资源
          最近更新 更多