Comment puis-je rechercher par programmation la clé d'une chanson juste en connaissant la série d'accords de la chanson?
J'ai demandé à des gens comment ils pourraient déterminer la tonalité d'un morceau, et ils ont tous dit qu'ils le font "par l'oreille" ou par "essai et erreur" et en disant que si un accord résout un morceau ou pas... Pour le musicien moyen, qui est probablement très bien, mais en tant que programmeur qui n'est pas vraiment la réponse que je cherchais.
J'ai donc commencé à regarder pour la musique, les bibliothèques connexes pour voir si quelqu'un d'autre a écrit un algorithme pour encore. Mais même si j'ai trouvé une très grande bibliothèque, appelée "tonal" sur GitHub: https://danigb.github.io/tonal/api/index.html je ne pouvais pas trouver une méthode qui permettrait d'accepter un tableau d'accords et de revenir sur la touche.
Mon choix de la langue sera JavaScript (NodeJs), mais je ne suis pas nécessairement à la recherche d'un JavaScript réponse. Le Pseudo-code ou une explication qui peut être traduit en code sans trop de problème serait tout à fait bien.
Comme certains d'entre vous ont évoqué correctement, la clé dans une chanson peut changer. Je ne suis pas sûr si un changement dans la clé pourrait être détectés de manière fiable assez. Donc, pour l'instant, disons simplement, je suis à la recherche d'un algorithme qui permet une bonne approximation sur la clé d'une série d'accords.
...
Après recherche dans le cercle des quintes, je crois que j'ai trouvé un motif pour trouver tous les accords qui appartiennent à chaque clé. J'ai écrit une fonction getChordsFromKey(key)
pour que. Et en vérifiant les accords d'une série d'accords à l'encontre de toutes les clés, je peux créer un tableau contenant les probabilités de comment il est probable que la clé correspond à l'accord de la séquence: calculateKeyProbabilities(chordSequence)
. Et puis, j'ai ajouté une autre fonction estimateKey(chordSequence)
, qui prend les clés avec la plus grande probabilité de score et vérifie ensuite si le dernier accord de la corde de la séquence de l'un d'eux. Si c'est le cas, elle renvoie un tableau contenant uniquement un accord, sinon, elle retourne un tableau de tous les accords avec la plus grande probabilité de score.
Ce n'est un OK de travail, mais ça ne fonctionne toujours pas trouver la bonne clé pour beaucoup de chansons ou retourne plusieurs touches avec l'égalité des probabililty. Le principal problème étant accords comme A5, Asus2, A+, A°, A7sus4, Am7b5, Aadd9, Adim, C/G
etc. qui ne sont pas dans le cercle des quintes. Et le fait que, par exemple, la touche C
contient exactement les mêmes accords que la clé Am
, et G
le même que Em
et ainsi de suite...
Voici mon code:
'use strict'
const normalizeMap = {
"Cb":"B", "Db":"C#", "Eb":"D#", "Fb":"E", "Gb":"F#", "Ab":"G#", "Bb":"A#", "E#":"F", "B#":"C",
"Cbm":"Bm","Dbm":"C#m","Eb":"D#m","Fbm":"Em","Gb":"F#m","Ab":"G#m","Bbm":"A#m","E#m":"Fm","B#m":"Cm"
}
const circleOfFifths = {
majors: ['C', 'G', 'D', 'A', 'E', 'B', 'F#', 'C#', 'G#','D#','A#','F'],
minors: ['Am','Em','Bm','F#m','C#m','G#m','D#m','A#m','Fm','Cm','Gm','Dm']
}
function estimateKey(chordSequence) {
let keyProbabilities = calculateKeyProbabilities(chordSequence)
let maxProbability = Math.max(...Object.keys(keyProbabilities).map(k=>keyProbabilities[k]))
let mostLikelyKeys = Object.keys(keyProbabilities).filter(k=>keyProbabilities[k]===maxProbability)
let lastChord = chordSequence[chordSequence.length-1]
if (mostLikelyKeys.includes(lastChord))
mostLikelyKeys = [lastChord]
return mostLikelyKeys
}
function calculateKeyProbabilities(chordSequence) {
const usedChords = [ ...new Set(chordSequence) ] // filter out duplicates
let keyProbabilities = []
const keyList = circleOfFifths.majors.concat(circleOfFifths.minors)
keyList.forEach(key=>{
const chords = getChordsFromKey(key)
let matchCount = 0
//usedChords.forEach(usedChord=>{
// if (chords.includes(usedChord))
// matchCount++
//})
chords.forEach(chord=>{
if (usedChords.includes(chord))
matchCount++
})
keyProbabilities[key] = matchCount / usedChords.length
})
return keyProbabilities
}
function getChordsFromKey(key) {
key = normalizeMap[key] || key
const keyPos = circleOfFifths.majors.includes(key) ? circleOfFifths.majors.indexOf(key) : circleOfFifths.minors.indexOf(key)
let chordPositions = [keyPos, keyPos-1, keyPos+1]
// since it's the CIRCLE of fifths we have to remap the positions if they are outside of the array
chordPositions = chordPositions.map(pos=>{
if (pos > 11)
return pos-12
else if (pos < 0)
return pos+12
else
return pos
})
let chords = []
chordPositions.forEach(pos=>{
chords.push(circleOfFifths.majors[pos])
chords.push(circleOfFifths.minors[pos])
})
return chords
}
// TEST
//console.log(getChordsFromKey('C'))
const chordSequence = ['Em','G','D','C','Em','G','D','Am','Em','G','D','C','Am','Bm','C','Am','Bm','C','Em','C','D','Em','Em','C','D','Em','Em','C','D','Em','Em','C','D','Am','Am','Em','C','D','Em','Em','C','D','Em','Em','C','D','Em','Em','C','D','Em','Em','C','D','Em','Em','C','D','Em','Em','C','D','Em','Em','C','D','Em']
const key = estimateKey(chordSequence)
console.log('Example chord sequence:',JSON.stringify(chordSequence))
console.log('Estimated key:',JSON.stringify(key)) // Output: [ 'Em' ]