choraleIteratorAndKeyAnalysis.py

#

Music21 chorale and key analysis tools

from music21 import *
#

s is a music21 stream object.

If a unique key is given by key objects in the stream, returns this key object. If there are no key objects or if there are more than one keys identified, returns None.

def getKeyFromStream(s):
#
    keys = set()
    for el in s.flat.getElementsByClass('Key'):
        keys.add(el)
    if len(keys) == 1:
        return keys.pop()
    else:
        return None
#

corpus.chorales.Iterator "is a class for iterating over many chorales." See http://web.mit.edu/music21/doc/moduleReference/ moduleCorpusChorales.html#iterator for more details.

BCI = corpus.chorales.Iterator()
total = len(BCI)
#

Empty list to hold stream objects for every chorale iterated through below.

chorales = []
#

Iterate over all chorales in the corpus. Each chorale is parsed by default.

for i, chorale in enumerate(BCI):
#

Note this syntax within the print statement. See https://pyformat.info/ for more info on string formatting.

    print("%d out of %d chorales parsed" % (i + 1, total))
    chorales.append(chorale)
#

One-line version of the code above, in this case limited to chorales 1-10.

chorales = [chorale for chorale in corpus.chorales.Iterator(1, 10)]

for chorale in chorales:
#

get labeled key for current chorale

    current_key = getKeyFromStream(chorale)
    if current_key is not None:
#

Title and other information can be found in the metadata object. See http://web.mit.edu/music21/doc/ moduleReference/moduleMetadata.html for more details.

        print("Chorale title:", chorale.metadata.title)
        print("Labeled key:", current_key)
#

Analyze the key of the chorale using a key-analysis algorithm. We'll go over these details later in the course. Note that s.analyze() is equivalent to analysis.discrete.analyzeStream(s). See http://web.mit.edu/music21/doc/moduleReference/ moduleAnalysisDiscrete.html#keyweightkeyanalysis for more details.

        k = chorale.analyze('key')
        print("Analyzed key:", k)
#

Key objects derived from analyze have additional properties, correlationCoefficient which measures the correlation between the expected pitch-class distribution for the analyzed key and the actual distribution in the analyzed stream.

        print("Correlation coefficient:", k.correlationCoefficient)
#

alternateInterpretations is a list of key objects for the remaining 23 major and minor keys sorted from most to least likely.

        alt_keys = k.alternateInterpretations
#

Print the three most likely alternate keys and their correlation coefficients.

        for alt_key in alt_keys[:3]:
            print("Top alternate keys:", alt_key,
                "Correlation coefficient:", alt_key.correlationCoefficient)
            
        match = current_key == k
        if match:
            print("Key analysis matches labeled key!")
        else:
            print("Key analysis does not match labeled key! :-(")
#

.index(x) finds the first index of a list that matches x. If there is no match, index returns ValueError. >>> a = [1, 2, 3]
>>> a.index(2)
1
>>> a.index(4)
ValueError: 4 is not in list

            key_index = alt_keys.index(current_key) + 2
            print("Labeled key was the number %d analyzed key" % key_index)
        print()