from music21 import * # activate library
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
Ch. 9 - Event counts
Install music21
and other elements needed to run this in Colab environment. Press play and wait for all commands to be executed - this initial command might take some time as it needs to build the musi21 environment.
Event counts
from music21 import * # activate library
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Define pieces
# These two piece are related, same piece, different harmonisation
= corpus.parse('bach/bwv110.7.xml') # bwv110.7
bwv110_7 = corpus.parse('bach/bwv40.3.xml') bwv40_3
Extract key and trasponse to common tonic
= bwv110_7.analyze('key')
k print(k)
= interval.Interval(k.tonic, pitch.Pitch('C'))
i print(i)
= bwv110_7.transpose(i)
bwv110_7
= bwv40_3.analyze('key')
k print(k)
= interval.Interval(k.tonic, pitch.Pitch('C'))
i print(i)
= bwv40_3.transpose(i)
bwv40_3
print('====== Transposed')
= bwv110_7.analyze('key')
t print(t)
= bwv40_3.analyze('key')
t print(t)
b minor
<music21.interval.Interval M-7>
g minor
<music21.interval.Interval P-5>
====== Transposed
c minor
c minor
Calculate pitch-class distribution
# pitch-class
= analysis.pitchAnalysis.pitchAttributeCount(bwv110_7, 'pitchClass')
pcCount = range(0, 12)
pc = ('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B')
pitchclass = [[i, pcCount[i]]for i in pc]
l_pcCount = pd.DataFrame(data=l_pcCount, columns=['pc_nro', 'count'])
d 'Percentage'] = d['count'] / sum(d['count'])
d["Pitch-Class"] = pitchclass
d["Piece"] = 'BWV 110/7'
d[
= analysis.pitchAnalysis.pitchAttributeCount(bwv40_3, 'pitchClass')
pcCount2 = [[i, pcCount2[i]]for i in pc]
l_pcCount2 = pd.DataFrame(data=l_pcCount2, columns=['pc_nro', 'count'])
d2 'Percentage'] = d2['count'] / sum(d2['count'])
d2["Pitch-Class"] = pitchclass
d2["Piece"] = 'BWV 40/3'
d2[
= pd.concat([d, d2]) PC
Calculate interval distribution
# intervals
#| echo: true
#| eval: true
=[]
df=[]
df2= dict() # add empty dictionary
counts for x in range(-12, 13):
= 0
counts[x]
for part in bwv110_7.recurse().parts:
= part.recurse(classFilter=('Note', 'Rest')) # this is ok but loses rests
p = p.melodicIntervals(skipOctaves=True,skipRests=True)
intervalStream1 = []
items for i in intervalStream1.recurse():
items.append(i.semitones)for j in items:
= counts.get(j, 0) + 1
counts[j]
= pd.DataFrame({'Interval': list(counts.keys()),
df 'Counts': list(counts.values())})
'Percentage'] = df['Counts'] / sum(df['Counts'])
df["Piece"] = 'BWV 110/7'
df[
for part in bwv40_3.recurse().parts:
= part.recurse(classFilter=('Note', 'Rest')) # this is ok but loses rests
p = p.melodicIntervals()
intervalStream1 = []
items for i in intervalStream1.recurse():
items.append(i.semitones)for j in items:
= counts.get(j, 0) + 1
counts[j]
= pd.DataFrame({'Interval': list(counts.keys()),
df2 'Counts': list(counts.values())})
'Percentage'] = df2['Counts'] / sum(df2['Counts'])
df2["Piece"] = 'BWV 40/3'
df2[
= pd.concat([df, df2]) IV
/var/folders/b0/vtr2rd_96119zlr64t5hvlgr0000gp/T/ipykernel_67002/4086787063.py:12: StreamIteratorInefficientWarning: melodicIntervals is not defined on StreamIterators. Call .stream() first for efficiency
intervalStream1 = p.melodicIntervals(skipOctaves=True,skipRests=True)
/var/folders/b0/vtr2rd_96119zlr64t5hvlgr0000gp/T/ipykernel_67002/4086787063.py:26: StreamIteratorInefficientWarning: melodicIntervals is not defined on StreamIterators. Call .stream() first for efficiency
intervalStream1 = p.melodicIntervals()
Calculate duration distribution
# durations
= bwv110_7.recurse().parts
part = part.recurse()
p = analysis.elements.attributeCount(p, 'quarterLength')
durCount = pd.DataFrame({'Duration': list(durCount.keys()),
du 'Counts': list(durCount.values())})
'Percentage'] = du['Counts'] / sum(du['Counts'])
du[
filter = (du['Duration'] < 10)
= du[filter]
du filter = (du['Duration'] >= 0.25)
= du[filter]
du "Piece"] = 'BWV 110/7'
du[
= bwv40_3.recurse().parts
part = part.recurse()
p = analysis.elements.attributeCount(p, 'quarterLength')
durCount = pd.DataFrame({'Duration': list(durCount.keys()),
du2 'Counts': list(durCount.values())})
'Percentage'] = du2['Counts'] / sum(du2['Counts'])
du2[
filter = (du2['Duration'] < 10)
= du2[filter]
du2 filter = (du2['Duration'] >= 0.25)
= du2[filter]
du2 "Piece"] = 'BWV 40/3'
du2[
= pd.concat([du, du2]) DU
/var/folders/b0/vtr2rd_96119zlr64t5hvlgr0000gp/T/ipykernel_67002/1590200641.py:4: StreamIteratorInefficientWarning: recurse is not defined on StreamIterators. Call .stream() first for efficiency
p = part.recurse()
/var/folders/b0/vtr2rd_96119zlr64t5hvlgr0000gp/T/ipykernel_67002/1590200641.py:17: StreamIteratorInefficientWarning: recurse is not defined on StreamIterators. Call .stream() first for efficiency
p = part.recurse()
Create plots
## Set graphic params
sns.set_theme()"whitegrid")
sns.set_style(= ["#b8b6b6", "#636362"]
colors = sns.set_palette(sns.color_palette(colors))
customPalette
sns.set_palette(customPalette)
"figure.figsize"] = [7.6, 10.0]
plt.rcParams["figure.autolayout"] = True
plt.rcParams[
= plt.subplots(3, 1)
f, axes = sns.barplot(x='Pitch-Class', y='Percentage', data=PC,
g ='v', ax=axes[0], hue='Piece')
orient
g.legend_.remove()0].text(11, 0.18, "$\chi^2=7.2, p=0.70$", horizontalalignment='right', size='x-small', color='black')
axes[
= sns.barplot(x='Interval', y='Percentage',
bar_plot =IV, orient='v', ax=axes[1], hue='Piece')
datafor index, label in enumerate(bar_plot.get_xticklabels()):
if index % 2 == 1:
True)
label.set_visible(else:
False)
label.set_visible(
1].text(25, 0.12, "$\chi^2=17.2, p=0.37$", horizontalalignment='right', size='x-small', color='black')
axes[
= sns.barplot(x='Duration', y='Percentage', data=DU,
h ='v', ax=axes[2], hue='Piece')
orient
2].text(5.25, 0.45, "$\chi^2=3.9, p=0.55$", horizontalalignment='right', size = 'x-small', color='black')
axes[
h.legend_.remove() plt.show()
Statistics
Pitch-class
from scipy import stats
import numpy as np
= PC.pivot(index='pc_nro', columns='Piece', values='count')
PC2 'Sum'] = PC2.sum(axis=1)
PC2[= PC2[PC2.Sum != 0]
PC3 = np.array([PC3['BWV 110/7'], PC3['BWV 40/3']])
obs2 = stats.chi2_contingency(obs2)
c, p, dof, exp print(f"_Chi_$^2$ value = {round(c,2)}, _p_-value = {round(p,3)}, _df_ = {dof}")
Chi\(^2\) value = 7.25, p-value = 0.702, df = 10
Intervals
= IV.pivot(index='Interval', columns='Piece', values='Counts')
IV2 'Sum'] = IV2.sum(axis=1)
IV2[= IV2[IV2.Sum != 0]
IV3 = np.array([IV3['BWV 110/7'], IV3['BWV 40/3']])
obs2 = stats.chi2_contingency(obs2)
c, p, dof, exp print(f"_Chi_$^2$ value = {round(c,2)}, _p_-value = {round(p,3)}, _df_ = {dof}")
Chi\(^2\) value = 17.2, p-value = 0.373, df = 16
Durations
= DU.pivot(index='Duration', columns='Piece', values='Counts')
DU2 = DU2.replace(np.nan,0)
DU2 'Sum'] = DU2.sum(axis=1)
DU2[= DU2[DU2.Sum != 0]
DU3
= np.array([DU3['BWV 110/7'], DU3['BWV 40/3']])
obs2 = stats.chi2_contingency(obs2)
c, p, dof, exp print(f"_Chi_$^2$ value = {round(c,2)}, _p_-value = {round(p,3)}, _df_ = {dof}")
Chi\(^2\) value = 3.94, p-value = 0.558, df = 5