Как я могу транспонировать музыкальные аккорды, используя JavaScript? - PullRequest
10 голосов
/ 29 октября 2011

Мне было интересно, как создать функцию 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

Я думаю, что это все, если я что-то забыл.

Итак, представьте, что у вас есть переменная javascript с именем chord и значение транспонирования 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';

Ответы [ 7 ]

9 голосов
/ 29 октября 2011

Как насчет чего-то вроде этого:

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 # / F", когда это действительно должно быть "C # / E #".

2 голосов
/ 29 октября 2011
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 шагов вниз).

1 голос
/ 31 августа 2017

Просто чтобы расширить ответ 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>
1 голос
/ 29 октября 2011

Определите ваши ключи с помощью объекта:

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;
0 голосов
/ 05 июня 2018

Во всех этих решениях отсутствует тот факт, что после переноса ноты ее необходимо преобразовать в резкую или плоскую в зависимости от тональности или аккорда.

Таким образом, 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));
0 голосов
/ 08 февраля 2017

Хорошо, теперь я подумал об этом, и у меня есть функциональный ответ. Это в стандартных тонах западного масштаба (извините северных европейцев).

Чтобы по-настоящему перенести аккорд, вам нужно 3 элемента информации: 1) имя CHORD, 2) OLDKEY и 3) NEWKEY. Модулировать с помощью AMOUNT не всегда достаточно (UP-2 от клавиши E - это F # или Gb?).

В основном вам нужно сохранить два расстояния - расстояние в шагах между CHORD и OLDKEY и расстояние в «буквах» между CHORD и OLDKEY - при отображении CHORD в NEWKEY.

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#"
0 голосов
/ 25 сентября 2013
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

...