# Owen Campbell # MAT 240E Homework 2 # # For the purposes of this assignment, key modulations will be defined (rather naively) as follows: # 'parallel minor' - The first chord and the last chord are of the same scale degree, but change from major to # major to minor # 'parallel major' - The first chord and the last chord are of the same scale degree, but change from major to # minor to major # 'relative minor' - The root of the last chord is 9 semitones above the root of the first chord and # there is a shift from major to minor tonality # 'relative major' - The root of the last chord is 3 semitones above the root of the first chord and # there is a shift from minor to major tonality # 'submediant' - The root of the last chord is 9 semitones above the root of the first chord but # there is no shift in tonality # 'mediant' - The root of the last chord is 3 semitones above the root of the first chord but # there is no shift in tonality # 'subdominant' - The root of the last chord is 5 semitones above the root of the first chord # 'dominant' - The root of the last chord is 7 semitones above the root of the first chord # 'unknown' - The first chord in the work differs from the last chord but the modulation does # not fall into any of the above categories # 'none' - The first chord in the work does not differ from the last chord # Hypothesis: Since these are classical works, it is very likely that the majority of the pieces will # involve a fairly predictable key modulation; specifically, one to a parallel, relative, # dominant or subdominant key. I originally intended to process all the works in the corpus, # but due to time constraints I am only using the Bach Chorales from music21 import * import ntpath import string # this function attempts to determine the interval, in semitones (+/-) between two pitches def degree_dif(start, end): if start == end: return 0 if end - start < 0: return end - start + 12 else: return end - start core = corpus.CoreCorpus() modulations = {'parallel minor': 0, 'parallel major': 0, 'relative minor': 0, 'relative major': 0, 'submediant': 0, 'mediant': 0, 'subdominant': 0, 'dominant': 0, 'unknown': 0, 'none': 0} works = core.getBachChorales() #print ntpath.basename(works[0]) for work in works: #name = ntpath.basename(work) work = corpus.parse(work) excerpt = work.measures(0,5) reduction = work.chordify().stripTies(retainContainers=True) chords = reduction.flat.getElementsByClass('Chord') nchords = len(chords) try: # attempt to get pitch class of first real chord first = harmony.chordSymbolFigureFromChord(chords[0], True) if first[0] == 'Chord Symbol Cannot Be Identified': idx = 2 while first == 'Chord Symbol Cannot Be Identified': first = harmony.chordSymbolFigureFromChord(chords[nchords + idx], True) idx += 1 fc_name = first[0].split('/', 1)[0] fc_color = first[1] fc_num = pitch.Pitch(str(fc_name).translate(None, string.ascii_lowercase)).pitchClass ft = first[1] # attempt to get pitch class of last chord last = harmony.chordSymbolFigureFromChord(chords[nchords - 1], True) if last[0] == 'Chord Symbol Cannot Be Identified': idx = 2 while last[0] == 'Chord Symbol Cannot Be Identified': last = harmony.chordSymbolFigureFromChord(chords[nchords - idx], True) idx += 1 lc_name = last[0].split('/', 1)[0] lc_color = last[1] lc_num = pitch.Pitch(str(lc_name).translate(None, string.ascii_lowercase)).pitchClass lt = last[1] distance = degree_dif(fc_num, lc_num) #print '~~~~~' #print 'first: {} ({})'.format(fc_num, fc_name) #print 'last: {} ({})'.format(lc_num, lc_name) #print 'dist: {}'.format(distance) if distance == 0: if fc_color == lc_color: modulations['none'] += 1 #print 'conclusion: none' elif fc_color == 'minor' and lc_color == 'major': modulations['parallel major'] += 1 #print 'conclusion: parallel major' elif fc_color == 'major' and lc_color == 'minor': modulations['parallel minor'] += 1 #print 'conclusion: parallel minor' else: # this will happen with less common chord labels like 'Gpedal' or 'Gsus' modulations['none'] += 1 #print 'conclusion: none' elif distance == 9: if fc_color == 'major' and lc_color == 'minor': modulations['relative minor'] += 1 #print 'conclusion: relative minor' else: # modulating to MAJOR 6 modulations['submediant'] += 1 #print 'conclusion: submediant' elif distance == 3: if fc_color == 'minor' and lc_color == 'major': modulations['relative major'] += 1 #print 'conclusion: relative major' else: # modulating to MINOR 3 modulations['mediant'] += 1 #print 'conclusion: mediant' elif distance == 5: modulations['subdominant'] += 1 #print 'conclusion: subdominant' elif distance == 7: modulations['dominant'] += 1 #print 'conclusion: dominant' else: modulations['unknown'] += 1 #print 'conclusion: unknown' #print except: continue import matplotlib as mpl mpl.rcParams['figure.figsize'] = (16, 4) pyplot.bar(range(len(modulations)), modulations.values(), align='center') pyplot.xticks(range(len(modulations)), modulations.keys(), rotation='vertical') pyplot.title('Modulations in Bach Chorales') pyplot.show() # Conclusion: # Though modulation to the parallel major was very common, the vast majority of the Bach Chorales ended # in the same key they began in, undermining my hypothesis that modulations would be most common. Perhaps # if the same analysis was performed on other Bach works or works by another composer we would see # different results. Furthermore, this analysis method has obvious limitations, and I would suspect that # many of the pieces which returned to the original key included some sort of modulation part way through # the work.