如何使用 JavaScript 转置音乐和弦

How do I transpose music chords using JavaScript?

本文关键字:音乐 转置 何使用 JavaScript      更新时间:2023-09-26

我想知道如何创建一个 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';

像这样一点怎么样:

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"(。

而且,很明显,我的函数只理解锐音,不理解平音,并且不理解键,因此,例如,当它真正应该是"C#/E#"时,transposeChord("C/E", 1)会给出"C#/F"。

只是为了扩展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>

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的旧浏览器的函数之前在此处添加代码。

我在jsFiddle上放了一个演示。

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

用对象定义键:

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;

好的,所以我现在已经考虑了很多,我有一个功能性的答案。它是标准的西方音阶音调(对不起北欧人(。

要真正转置和弦,您需要 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#"

所有这些解决方案都缺乏这样一个事实,即在转调音符后,需要根据键或和弦将其转换为尖音或平音。

因此,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));
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:上午7

我需要对吉他/尤克里里歌谱中的和弦线进行转置,并想出了以下函数,如果空间允许,可以保持间距,并重复使用 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/#。