Определить ключ песни своими аккордами
Как я могу программно найти ключ песни, просто зная последовательность аккордов песни?
Я спросил некоторых людей, как они определят ключ от песни, и все они говорили, что делают это "по уху" или "проб и ошибок", и сообщая, разрешает ли аккорд песню или нет... Для среднего музыканта, который вероятно, прекрасно, но как программист, который действительно не является тем ответом, который я искал.
Итак, я начал искать библиотеки, связанные с музыкой, чтобы узнать, написал ли кто-нибудь еще алгоритм для этого. Но хотя я нашел очень большую библиотеку под названием "tonal" на GitHub: https://danigb.github.io/tonal/api/index.html Я не смог найти метод, который бы принял массив аккорды и вернуть ключ.
Мой язык выбора будет JavaScript (NodeJs), но я не обязательно ищу ответ на JavaScript. Псевдокод или объяснение, которое может быть переведено в код без особых проблем, было бы полностью прекрасным.
Как некоторые из вас упомянули правильно, ключ в песне может измениться. Я не уверен, что изменение ключа может быть обнаружено достаточно надежно. Итак, на данный момент, скажем так, я ищу алгоритм, который хорошо аппроксимирует ключ данной последовательности аккордов.
Ответы
Ответ 1
Аккорды в песне определенного ключа являются преимущественно членами ключевой шкалы. Я думаю, вы могли бы получить хорошее приближение статистически (если данных достаточно), сравнивая преобладающие случайности в аккордах, перечисленных в ключевых подписях ключей.
См. https://en.wikipedia.org/wiki/Circle_of_fifths
Конечно, песня в любой клавише может/иметь случайности не в шкале клавиш, поэтому она, скорее всего, будет статистической аппроксимацией. Но в нескольких барах, если вы добавляете случайные ситуации и отфильтровываете все, кроме тех, которые происходят чаще всего, вы можете соответствовать ключевой сигнатуре.
Добавление: как правильно указывает Jonas w, вы можете получить подпись, но вы вряд ли сможете определить, является ли это основным или второстепенным ключом.
Ответ 2
Возможно, вы также сохраните структуру с ключами для каждого "поддерживаемого" шкалы, а в качестве значения - массив с аккордами, соответствующими этому масштабу.
Учитывая последовательность аккордов, вы можете начать с создания списка ключей на основе вашей структуры.
С несколькими совпадениями вы можете попытаться сделать обоснованное предположение. Например, добавьте другой "вес" в любой масштаб, соответствующий коренной ноте.
Ответ 3
Для массива таких тонов:
var tones = ["G","Fis","D"];
Сначала мы можем сгенерировать уникальный набор тонов:
tones = [...new Set(tones)];
Затем мы могли проверить наличие # и bs:
var sharps = ["C","G","D","A","E","H","Fis"][["Fis","Cis","Gis","Dis","Ais","Eis"].filter(tone=>tones.includes(tone)).length];
Затем сделайте то же самое с bs и получите результат с помощью:
var key = sharps === "C" ? bs:sharps;
Тем не менее, вы все еще не знаете, не слишком ли важны его основные или второстепенные, а многие компоненты не заботятся о верхних правилах (и изменили ключ между ними)...
Ответ 4
Вы можете использовать спиральный массив, 3D-модель для тональности, созданной Elaine Chew, которая имеет алгоритм обнаружения ключей.
Чуан, Чинг-Хуа и Элейн Чу. " Поиск полифонического аудио ключа с использованием алгоритма CEG спиральной матрицы." Multimedia and Expo, 2005. ICME 2005. Международная конференция IEEE. IEEE, 2005.
Моя недавняя модель натяжения, которая доступна в . jar file здесь, также выводит ключ (в дополнение к мерам натяжения) основанный на спиральной матрице. Он может либо взять файл musicXML или текстовый файл в качестве входных данных, который просто принимает список имен высоты для каждого "временного окна" в вашей части.
Herremans D., Chew E.. 2016. Ленты растяжения: квантование и визуализация тонального напряжения. Вторая Международная конференция по технологиям нотной музыки и представления (TENOR). 2:. 8-18
Ответ 5
Вот что я придумал. Все еще новые с современными JS так извиняются за беспорядочность и плохое использование карты().
Я осмотрел внутренности тональной библиотеки, у нее есть функция scalees.detect(), но это было бесполезно, поскольку она требовала присутствия каждой заметки. Вместо этого я использовал его как вдохновение и сгладил прогрессию в простой список заметок и проверил это во всех транспозициях как подмножество всех возможных масштабов.
const _ = require('lodash');
const chord = require('tonal-chord');
const note = require('tonal-note');
const pcset = require('tonal-pcset');
const dictionary = require('tonal-dictionary');
const SCALES = require('tonal-scale/scales.json');
const dict = dictionary.dictionary(SCALES, function (str) { return str.split(' '); });
//dict is a dictionary of scales defined as intervals
//notes is a string of tonal notes eg 'c d eb'
//onlyMajorMinor if true restricts to the most common scales as the tonal dict has many rare ones
function keyDetect(dict, notes, onlyMajorMinor) {
//create an array of pairs of chromas (see tonal docs) and scale names
var chromaArray = dict.keys(false).map(function(e) { return [pcset.chroma(dict.get(e)), e]; });
//filter only Major/Minor if requested
if (onlyMajorMinor) { chromaArray = chromaArray.filter(function (e) { return e[1] === 'major' || e[1] === 'harmonic minor'; }); }
//sets is an array of pitch classes transposed into every possibility with equivalent intervals
var sets = pcset.modes(notes, false);
//this block, for each scale, checks if any of 'sets' is a subset of any scale
return chromaArray.reduce(function(acc, keyChroma) {
sets.map(function(set, i) {
if (pcset.isSubset(keyChroma[0], set)) {
//the midi bit is a bit of a hack, i couldnt find how to turn an int from 0-11 into the repective note name. so i used the midi number where 60 is middle c
//since the index corresponds to the transposition from 0-11 where c=0, it gives the tonic note of the key
acc.push(note.pc(note.fromMidi(60+i)) + ' ' + keyChroma[1]);
}
});
return acc;
}, []);
}
const p1 = [ chord.get('m','Bb'), chord.get('m', 'C'), chord.get('M', 'Eb') ];
const p2 = [ chord.get('M','F#'), chord.get('dim', 'B#'), chord.get('M', 'G#') ];
const p3 = [ chord.get('M','C'), chord.get('M','F') ];
const progressions = [ p1, p2, p3 ];
//turn the progression into a flat string of notes seperated by spaces
const notes = progressions.map(function(e) { return _.chain(e).flatten().uniq().value(); });
const possibleKeys = notes.map(function(e) { return keyDetect(dict, e, true); });
console.log(possibleKeys);
//[ [ 'Ab major' ], [ 'Db major' ], [ 'C major', 'F major' ] ]
Некоторые недостатки:
- не дает требуемой энгармонической ноты. В p2 более правильным ответом является С# major, но это может быть исправлено путем проверки как-то с первоначальной прогрессией.
- не будет иметь дело с "украшениями" на аккорды, которые находятся вне ключа, что может происходить в поп-песнях, например. CMaj7 FMaj7 GMaj7 вместо C F G. Не знаю, насколько это распространено, не слишком много, я думаю.