(Version 2022_01_03)
In diesem Tutorial werden Sie anhand von Musikbeispielen einfache computergestützten Möglichkeiten statistischer Abfragen kennenlernen.
Das Tutoriums soll es Ihnen ermöglichen, mit den vorgestellten computergestützten Methoden eigene Musikbeispiele (Notendateien) zu untersuchen und Vergleiche zwischen verschiedenen Musikstücken durchzuführen. Dabei geht es immer auch um die Formulierung bestimmter analytischer Fragestellungen und die Interpretation der jeweiligen Ergebnisse.
from music21 import *
import csv
from IPython.display import HTML, display
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
environment.set('autoDownload', 'allow')
# Mit diesen Befehlen wird nicht nur 'music21',
# sondern darüber hinaus die Bibliotheken 'numpy' und 'pandas' für statische Auswertungen
# sowie 'matplotlib' für grafische Darstellungen geladen.
# mit den folgenden Befehlen wird die Formtierung für die Tabellen festgelegt,
# die weiter unten dargestellt werden:
pd.set_option('display.max_rows', 200)
pd.set_option('display.max_columns', 200)
pd.set_option('display.width', 200)
# Und zuletzt Befehle für den Datenexport (Sie müssen diese Befehle nicht verstehen - ein fach auf 'Run' drücken!):
def export_as_csv(data, columns, save_at, do_print= False, do_return_pd= False, sep=',', index=False, header=True ):
"""
data (list): nd array as list
columns (list): list of column header in strings
save_at (str) : path the csv to be saved
"""
pd_data = pd.DataFrame(data, columns=columns)
pd_data.to_csv(save_at, sep=';', index=index, header=header)
if do_print:
display(HTML(pd_data.to_html(index=False)))
if do_return_pd:
return pd_data
Nun laden wir das Kyrie aus der Messe "Missa La sol fa re mi" von Josquin (Jos1102):
music = converter.parse('https://analyse.hfm-weimar.de/database/02/PrJode_Jos1102_COM_1-5_MissaLasol_002_00137.xml'
)
# 'music' wird als Name (Variable) für die geladene ('geparste') Notendatei gewählt
music.measures(1, 4).show()
Man kann sich das Stück auch anhören - allerdings nur in einer Version mit MIDI-Klängen (und nur in den Browsern Chrome und Firefox):
music.show('midi')
Alle in der MusicXML-Datei gespeicherte Metadaten zum untersuchten Werk (Komponist, Werktitel, Entstehungsjahr, usw.) können mit dem folgenden Befehl angezeigt werden:
music.metadata.all()
[('None', 'Jesse Rodin'), ('composer', 'Josquin des Prez'), ('movementName', 'Kyrie'), ('number', '1'), ('parentTitle', 'Missa La sol fa re mi')]
Nun die einfachen statistischen Abfragen aus dem letzten Tutorial: Anzahl der Stimmen, Anzahl der Töne (insgesamt und pro Stimmen), Länge in Takten, Ambitus, die wir bereits im Grundlagen-Tutorial kennengelernt haben:
len(music.getElementsByClass(stream.Part))
# Anzahl der Stimmen
# 'local' wird als Name (Variable) für die geladene ('geparste') Notendatei gewählt
4
len(music.getElementsByClass(stream.Part)[0].getElementsByClass(stream.Measure))
# Länge in Takten
58
Gibt es Unterschiede zwischen den Ober- und Unterschiede bzgl. der Notenzahl und des Ambitus?
a = len(music.getElementsByClass(stream.Part))
for x in range(a):
print(len(music.parts[x].recurse().notes))
# Anzahl der Töne per Stimme
164 162 121 104
music.analyze('ambitus')
# Ambitus (alle Stimmen)
<music21.interval.Interval P19>
a = len(music.getElementsByClass(stream.Part))
for x in range(a):
print(music.parts[x].analyze('ambitus'))
# Ambitus per Stimme
<music21.interval.Interval P11> <music21.interval.Interval P12> <music21.interval.Interval m10> <music21.interval.Interval M9>
Zur Charakterisierung einzelner Musikstücke und zum Vergleich zwischen verschiedenen Musikstücken kann es sinnvoll sein, die Häufigkeit bestimmter Elemente (Tonhöhen, Dauernwerte, Akkorde etc.) zu bestimmen. Für solche Fragen lassen Häufigkeitstabellen und grafische Darstellungen, sog. Histogramme erstellen.
Welche Töne tauchen wie häufig auf? Wie diatonisch ist der Tonraum der Missa, wie viele zusätzliche chromatische Töne tauchen auf?
music.plot('histogram', 'pitch')
Was können wir beobachten? Die Musik ist eindeutig diatonisch; die zusätzlichen Töne (F#, G# und Bb) sind dagegen äußerst selten.
%matplotlib
# Durch diesen Befehl werden alle folgenden grafischen Darstellungen in einem Popup-Fenster geöffnet
# und können dort vergrößert werden (vgl. Tutorial Grundlagen).
music.plot('histogram', 'pitch')
Using matplotlib backend: Qt5Agg
%matplotlib inline
# Durch diesen Befehl wird der Popup-Befehl wieder außer Kraft gesetzt.
Durch den folgenden Befehl wird eine Liste der Tonhöhenhäufigkeiten angezeigt und als csv-Datei exportiert (csv = comma separated variables; lesbar und weiterverarbeitbar u.a. in Excel oder im Text-Editor). Durch den Export lassen sich Tabellen zum Stückvergleich und zur Korpusanalyse generieren. Die csv-Datei wird in dem Ordner gespeichert, den Sie in Ihren Settings als "settings['directoryScratch']" festgelegt haben und kann mit einem Texteditor oder einem Tabellenkalkulationsprogramm (z.B. Excel) geöffnet werden.
nameOctaveCount = analysis.pitchAnalysis.pitchAttributeCount(music, 'nameWithOctave')
notes_nameOctaveCount = [[i, nameOctaveCount[i]]for i in sorted(nameOctaveCount)]
export_as_csv(data=notes_nameOctaveCount,
columns=['Notes With Octaves', 'Pitch'],
save_at='NotesWithOctaves_test.csv',
do_print=False, # Mit do_print=True wird die Tabelle im Browser ausgegeben.
sep=';') # Als Trennzeichen wird ein Semikolon verwendet.
Ermitteln wir nun im Vergleich zu Josquin die Tonhöhenverteilung in der Ballade h-moll Franz Liszt. Was fällt auf?
liszt = converter.parse('https://analyse.hfm-weimar.de/database/03/LiFra_S171_COM_COM_BalladeNo2_003_00822.xml')
liszt.plot('histogram', 'pitch')
Die Tönhöhen lassen sich in einem zweiten Schritt zu Tonhöhenklassen zusammenfassen. Dadurch wird das Ergebnis übersichtlicher.
music.plot('histogram', 'pitchclass')
Durch folgenden Befehlsfolge können die Häufigkeiten der einzelnen Tonhöhenklassen in einer Liste angezeigt und als csv-Datei exportiert werden:
pcCount = analysis.pitchAnalysis.pitchAttributeCount(music, 'pitchClass')
l_pcCount = [[i, pcCount[i]]for i in sorted(pcCount)]
export_as_csv(data=l_pcCount,
columns=['Pitch Class', 'Pitch'],
save_at='pcCount.csv',
do_print=False, # Mit do_print=True wird die Tabelle im Browser ausgegeben.
sep=';') # Als Trennzeichen wird ein Semikolon verwendet.
Und auch hier der Vergleich zur h-moll-Ballade von Liszt:
liszt.plot('histogram', 'pitchclass')
Welche Dauernwerte verwendet Josquin in seiner Missa und wie häufig kommen diese jeweils vor?
# Für das Dauernwerte-Histogram muss eine andere Befehlsmethode angewendet werden.
# Der graph-Befehl muss mit dem run-Befehl kombiniert werden.
dauern = graph.plot.HistogramQuarterLength(music)
dauern.run()
Die Notenwerte werden als Vielfache einer Viertel (Quarter Lenght) gezählt. Der Notentext besteht somit vorwiegend aus Halben (2.0) und Ganzen (4.0); andere Notenwerte sind seltener.
# Und hier wieder der Befehl für den Export als csv-Datei:
dauern.extractData()
hist_data = dauern.data
hist_data = [[i[0], i[1]] for i in hist_data]
export_as_csv(data=hist_data,
columns=['Quater Length', 'Frequency'],
save_at='dauern_hist_data.csv',
do_print=False, # Mit do_print=True wird die Tabelle im Browser ausgegeben.
sep=';') # Als Trennzeichen wird ein Semikolon verwendet.
Wie deutlich wird das Metrum in den einzelnen Stimmen einer Komposition – etwa durch die Platzierung der Töne auf Taktanfängen und den metrisch wichtigen Positionen innerhalb des Taktes (z.B. der Taktmitte oder auf den Viertelpositionen)? Hierzu lässt sich in eine Liste der Häufigkeiten von Tönen auf den verschiedenen metrischen Positionen in den einzelnen Stimmen anzeigen.
Die metrischen Akzente folgen für die gängigen Taktarten folgender Gewichtung (jeweils für eine Folge von Achtelnoten): im 4/4-Takt: 1.0, 0.125, 0.25, 0.125, 0.5, 0.125, 0.25, 0.125; im 3/4-Takt: 1.0, 0.25, 0.5, 0.25, 0.5, 0.25 usw.
Das heißt also: Der Taktanfang erhält mit 1.0 das höchste Gewicht. 0.5 = mittleres Gewicht, 0.25 geringeres Gewicht usw.
# Mit dem folgenden Befehl lässt sich eine Liste der Tonanfänge
#in der Bassstimme mit dem jeweiligen Zeitpunkt im Takt erzeugen:
bass = music.parts[3]
for n in bass.flat.notes[:4]: # [:4] -> druckt nur die ersten 4 Einträge
print(n, n.beatStrength)
<music21.note.Note A> 1.0 <music21.note.Note G> 0.5 <music21.note.Note F> 1.0 <music21.note.Note D> 0.5
# Der Befehl ist etwas komplizierter.
# Sie brauchen diese kleinen Befehls-Skripte nicht zu verstehen.
# Beachten Sie aber, welche Variablen verwendet bzw. definiert werden:
# Im folgenden Fall 'music' und 'bass'.
# An diesen Stellen können Sie andere Variablen platzieren, wenn Sie andere Stücke / Stimmen auswerten wollen!
# Mit diesen Befehlen werden die Noten- und Metrumsinformationen aus der Basstimme extrahiert.
bass = music.parts[3]
n_list, n_beat_strength = [],[]
l_bs = []
for n in bass.flat.notes:
c_n = str(n)
n_list.append(c_n.replace('<music21.note.Note ','').replace('>',''))
n_beat_strength.append(float(n.beatStrength))
pd_bs = pd.DataFrame(np.array([n_list, n_beat_strength]).T, columns=['Notes', 'Beat_Strength'])
n_uni = pd_bs['Notes'].unique()
bs_uni = pd_bs['Beat_Strength'].unique()
# Nun werden Histogram und csv-Liste angezeigt und gespeichert.
bs_int_indi = []
for c_bs_uni in bs_uni:
l = len(pd_bs.loc[(pd_bs['Beat_Strength']==c_bs_uni)])
bs_int_indi.append([c_bs_uni, l])
pd_bs_indi_data = export_as_csv(data=bs_int_indi,
columns=['Beat_Strength', 'Occurrence'],
save_at='beat_strength_freq_02.csv',
do_print=True, do_return_pd=True)
np_bs_indi_data = pd_bs_indi_data.to_numpy()
fig2 = plt.figure()
ax2 = fig2.add_subplot(111)
ax2.bar(np_bs_indi_data[:,0], np_bs_indi_data[:,1], width=0.5 ,color='darkslateblue', alpha=0.8)
ax2.set_title('Beat Strength')
ax2.set_xlabel('Beat Strength')
ax2.set_ylabel('Occurrence')
plt.show()
Beat_Strength | Occurrence |
---|---|
1.0 | 38 |
0.5 | 42 |
0.25 | 24 |
Im Falle des "3/1-Taktes" entspricht die 1.0 den Taktanfängen, der zweiten und dritten Ganzen-Position im Takt, die 0.25 den verbleibenden Halb-Positionen.
Gehen wir nun zurück zum Beispiel von Josquin. Wie häufig kommen bestimmte Intervallschritte in den einzelnen Stimmen vor? Sind da alle Stimmen ähnlich gelagert – oder gibt es z.B. im Bass mehr Sprünge, in den Mittelstimmen mehr Schritte?
Die Beantwortung dieser Frage kann bei music21 nur in mehreren Schritten erfolgen: Zunächst muss eine Stimme ausgewählt und mit einer neuen Variablen bezeichnet werden.
Superius = 0, Altus = 1, Tenor = 2, Bassus = 3
Wählen wir die Bassstimme:
bass = music.parts[3]
bass.show()
Mit dem folgenden Befehl lassen sich die Intervallschritte in der Bassstimme ('bass') zählen:
intervalle = analysis.discrete.MelodicIntervalDiversity()
intervalle.countMelodicIntervals(bass)
# Die Intervallbenennung folgt dem Englischen:
# M2 = große (Major) Sekunde,
# m3 = kleine (minor) Terz
# P4 = reine (Perfect) Quarte
# usw.
{'M2': [<music21.interval.Interval M2>, 47], 'm3': [<music21.interval.Interval m3>, 24], 'P4': [<music21.interval.Interval P4>, 8], 'm7': [<music21.interval.Interval m7>, 1], 'P8': [<music21.interval.Interval P8>, 2], 'M3': [<music21.interval.Interval M3>, 10], 'm2': [<music21.interval.Interval m2>, 4], 'P5': [<music21.interval.Interval P5>, 1]}
Nun der Vergleich mit der Altus-Stimme. Was lässt sich hieraus über die Art der Fortschreitung der beiden Stimmen sagen? Ist sie ähnlich oder unterschiedlich?
altus = music.parts[2]
intervalle.countMelodicIntervals(altus)
{'M2': [<music21.interval.Interval M2>, 60], 'm3': [<music21.interval.Interval m3>, 21], 'P4': [<music21.interval.Interval P4>, 13], 'm2': [<music21.interval.Interval m2>, 15], 'P5': [<music21.interval.Interval P5>, 8], 'P8': [<music21.interval.Interval P8>, 1]}
Mit den folgenden Befehlen lässt sich die Liste der Intervallhäufigkeiten anzeigen exportieren bzw. als Histogramm anzeigen.
altus = music.parts[2]
m_int_cls = intervalle.countMelodicIntervals(altus)
int_cls = [[i, m_int_cls[i][1]] for i in m_int_cls]
pd_int_clas_data = export_as_csv(data=int_cls,
columns=['Interval Class', 'Occurrence'],
save_at='interval_class.csv',
do_print=True, do_return_pd=False)
Interval Class | Occurrence |
---|---|
M2 | 60 |
m3 | 21 |
P4 | 13 |
m2 | 15 |
P5 | 8 |
P8 | 1 |
# Durch die folgenden Befehle wird ein Histogramm der Intervallhäufigkeiten erzeugt.
d_st, d_st_nm = [],[]
for semitones in range(128):
tempInt = interval.Interval(semitones)
d_st.append(semitones)
d_st_nm.append(tempInt.name)
st_dict = dict(zip(d_st_nm, d_st)) # dict to sort
r_st_dict = {v: k for k, v in st_dict.items()}
int_cls_ids = [[st_dict[i[0]],i[1]] for i in int_cls]
sort_int_cls_ids = np.array(sorted(int_cls_ids,key=lambda x: x[0]))
fig3 = plt.figure()
ax3 = fig3.add_subplot(111)
x = sort_int_cls_ids[:,0]
x = [r_st_dict[i[0]] for i in sort_int_cls_ids]
ax3.bar(x, sort_int_cls_ids[:,1], width=0.5 ,color='darkslateblue', alpha=0.8)
ax3.set_title('')
ax3.set_xlabel('Interval Class')
ax3.set_ylabel('Occurrence')
ax3.set_xticks(np.arange(np.shape(sort_int_cls_ids)[0]))
plt.show()
Wir haben uns bereits die Häufigkeiten von Tonhöhen bzw. Tonhöhenklassen angeschaut. Nun könnte man sagen: Längere Töne haben natürlich mehr Gewicht als kurze Töne oder Töne zwischen den Zählzeiten. Diesen Gedanken können wir weiterverfolgen, indem wir uns kombinierte, ‚doppelte‘ oder ‚bivariate‘ Häufigkeitsverteilungen: Also z.B. die Häufigkeiten der Tonhöhen für jeweils die verschiedenen Dauernwerte. Hierum soll es im Folgenden anhand von zwei Beispielen gehen.
Beispiel 1: Dauern pro Tonhöhenklassen. Gibt es Unterschiede bei den Dauernwerten bzgl. der verschiedenen Tonhöhenklassen?
scatter1 = graph.plot.ScatterWeightedPitchClassQuarterLength(music)
scatter1.run()
# (Das log2 auf der x-Achse bedeutet, dass die verschiedenen Notenwerte nicht linear angeordnet sind.)
Was lässt sich aus der Grafik herauslesen?
Es gibt eine weitere Darstellungsweise des Verhältnisses von Tonhöhen bzw. Tonhöhenklassen und Tondauern: Ein 3-dimensionales Histogramm:
music.plot('3dbars', 'pitch', 'quarterLength')
music.plot('3dbars', 'pitchclass', 'quarterLength')
Beispiel 2: Töne auf den unterschiedlich akzentuierten metrischen Positionen (s. oben, 2.4) im Verhältnis zu Tonhöhenklassen. Hierzu ist wieder ein etwas komplexerer Befehl notwendig:
n_uni_int_dict = dict(zip(n_uni, np.arange(len(n_uni))))
bs_uni_int_dict = dict(zip(bs_uni, np.arange(len(bs_uni))))
for c_n_uni in n_uni:
for c_bs_uni in bs_uni:
l = len(pd_bs.loc[(pd_bs['Notes']==c_n_uni) & (pd_bs['Beat_Strength']==c_bs_uni)])
l_bs.append([c_n_uni, c_bs_uni, l])
pd_bs_data = export_as_csv(data=l_bs,
columns=['Note' ,'Beat_Strength', 'Occurrence'],
save_at='beat_strength_freq.csv',
do_print=True, do_return_pd=True)
np_bs_data = pd_bs_data.to_numpy()
# --------- plotting -------
#print("Dict",n_uni_int_dict)
#print("Dict",bs_uni_int_dict)
plt_bs_data = np.zeros(np.shape(pd_bs_data))
for i in range(np.shape(np_bs_data)[0]):
plt_bs_data[i][0] = n_uni_int_dict[np_bs_data[i][0]]
plt_bs_data[i][1] = bs_uni_int_dict[np_bs_data[i][1]]
plt_bs_data[i][2] = np_bs_data[i][2]
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
numele= np.shape(np_bs_data)[0]
x = plt_bs_data[:,0]
y = plt_bs_data[:,1]
z = np.zeros(numele)
dx = 0.5 * np.ones(numele)
dy = 0.3 * np.ones(numele)
dz = plt_bs_data[:,2]
cmap = cm.get_cmap('jet') # Get desired colormap
max_height = np.max(dz) # get range of colorbars
min_height = np.min(dz)
rgba = [cmap((k-min_height)/max_height) for k in dz]
ax1.set_xticks(np.arange(len(n_uni)))
ax1.set_yticks(np.arange(len(bs_uni)))
ax1.bar3d(x, y, z, dx, dy, dz, color=rgba, zsort='average')
ax1.set_xticklabels(list(n_uni))
ax1.set_yticklabels([str(i) for i in bs_uni])
ax1.set_xlabel('Notes')
ax1.set_ylabel('Beat Strength')
ax1.set_zlabel('Occurrence')
#plt.savefig("Your_title_goes_here")
Note | Beat_Strength | Occurrence |
---|---|---|
A | 1.0 | 8 |
A | 0.5 | 7 |
A | 0.25 | 10 |
G | 1.0 | 4 |
G | 0.5 | 13 |
G | 0.25 | 0 |
F | 1.0 | 4 |
F | 0.5 | 4 |
F | 0.25 | 4 |
D | 1.0 | 3 |
D | 0.5 | 10 |
D | 0.25 | 5 |
E | 1.0 | 11 |
E | 0.5 | 2 |
E | 0.25 | 4 |
C | 1.0 | 4 |
C | 0.5 | 4 |
C | 0.25 | 1 |
B | 1.0 | 2 |
B | 0.5 | 0 |
B | 0.25 | 0 |
B- | 1.0 | 2 |
B- | 0.5 | 2 |
B- | 0.25 | 0 |
Text(0.5, 0, 'Occurrence')
Vergleichende statistische Abfragen über mehrere Stücke:
Wir haben uns die Ergebnisse bislang nur anhand jeweils eines Stückes angeschaut. Wie stellt sich nun aber die Lage dar, wenn wir mehrere Stücke, z.B. mehrere Sätze von Josquin, miteinander – und mit den Stücken eines seiner Zeitgenossen – vergleichen? Gibt es da tatsächlich stilistische Regelmäßigkeiten – oder stilistische Unterschiede?
Wählen Sie Kompositionen Ihrer Wahl (verschiedene Gattungen, Komponisten und Epochen) und vergleichen Sie diese Stücke hinsichtlich der Häufigkeiten von Tonhöhen, Tonhöhenklassen, Notenwerten und Intervallen. Beschreiben Sie die Ergebnisse!