42 votes

Déterminer la clé d'une chanson par ses accords

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

14voto

RichGoldMD Points 1195

Les accords dans un morceau de clé particulière, sont principalement des membres de la clé de l'échelle. J'imagine que vous pouvez obtenir une bonne approximation statistiquement (si il ya suffisamment de données) en comparant la principale altérations dans les accords figurant à la clé de signatures de clés.

Voir https://en.wikipedia.org/wiki/Circle_of_fifths

Bien sûr, une chanson en toute touche est/peut avoir des altérations pas dans les clés de l'échelle, de sorte qu'il serait susceptible d'être une statistique rapprochement. Mais au cours de plusieurs bars, si vous ajoutez les altérations et de filtrer toutes, mais celles qui se produisent le plus souvent, vous pourriez être en mesure de correspondre à une clé de signature.

Addendum: comme Jonas w correctement points, vous pouvez être en mesure d'obtenir la signature, mais vous ne sera probablement pas en mesure de déterminer s'il est majeur ou mineur.

11voto

Lucas Points 496

Voici ce que j'ai trouvé. Encore du nouveau avec moderne JS alors, toutes mes excuses pour le désordre et la mauvaise utilisation de la carte().

J'ai regardé autour de l'intérieur de la tonalité de la bibliothèque, il a une fonction d'échelles.détecter(), mais il n'était pas bon, car il exigeait de chaque note présents. Au lieu de cela, je l'ai utilisé comme source d'inspiration et aplatie de la progression dans une simple note de la liste et examiner ce dans tous les transpositions comme un sous-ensemble de toutes les échelles possibles.

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

Quelques inconvénients:
- ne donne pas l'enharmonique note que vous voulez nécessairement. En p2, le plus de bonne réponse est C# majeur, mais cela pourrait être fixé par la vérification d'une certaine manière à l'origine de la progression.
- ne pas traiter avec des "décorations" pour les accords qui sont hors de la clé, ce qui pourrait se produire dans des chansons pop, par exemple. CMaj7 FMaj7 GMaj7 au lieu de C F G. ne savez Pas comment la commune, ce n'est pas de trop je pense.

8voto

Jonas Wilms Points 52419

Étant donné un tableau de tons comme celui-ci:

 var tones = ["G","Fis","D"];
 

Nous pouvons d’abord générer un ensemble unique de sons:

 tones = [...new Set(tones)];
 

Ensuite, nous pourrions vérifier l’apparence de # et bs:

 var sharps = ["C","G","D","A","E","H","Fis"][["Fis","Cis","Gis","Dis","Ais","Eis"].filter(tone=>tones.includes(tone)).length];
 

Faites de même avec bs et obtenez le résultat avec:

 var key = sharps === "C" ? bs:sharps;
 

Cependant, vous ne savez toujours pas si c'est majeur ou mineur , et de nombreux composants ne se soucient pas des règles supérieures (et ont changé la clé entre les deux) ...

7voto

Vous pourrez peut-être aussi conserver une structure avec des clés pour chaque gamme "prise en charge", avec comme valeur un tableau avec des accords correspondant à cette gamme.

À partir d’une progression d’accord, vous pouvez commencer par faire une liste de clés basée sur votre structure.

Avec plusieurs correspondances, vous pouvez essayer de faire une supposition éclairée. Par exemple, ajoutez un autre "poids" à toute balance qui correspond à la note fondamentale.

6voto

dorien Points 818

Vous pouvez utiliser la spirale de tableau, un modèle 3D pour la tonalité créé par Elaine Mâcher, qui dispose d'une clé algorithme de détection.

Chuan, Ching-Hua, et Elaine Mâcher. "Audio polyphonique constatation clé à l'aide de la spirale de la matrice de CEG algorithme." Du multimédia et de l'Expo, 2005. ICME 2005. IEEE Conférence Internationale sur la. IEEE, 2005.

Mes récentes tensions modèle, qui est disponible dans une .fichier jar ici, aussi les sorties de la clé (en plus de la tension de mesures basées sur la spirale de tableau. Il peut soit prendre un fichier musicXML ou un fichier texte en entrée qui prend juste une liste de nom des notes pour chaque "fenêtre de temps" dans votre pièce.

Herremans D., Mâcher E.. 2016. La Tension des rubans: Quantifier et de visualiser des tons de tension. Deuxième Conférence Internationale sur les Technologies pour la Notation de la Musique et de la Représentation (TÉNOR). 2:8-18.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X