Notes#
Show imports
import os
import dimcat as dc
import ms3
import pandas as pd
import plotly.express as px
from dimcat import filters, plotting
import utils
pd.set_option("display.max_rows", 1000)
pd.set_option("display.max_columns", 500)
Show source
RESULTS_PATH = os.path.abspath(os.path.join(utils.OUTPUT_FOLDER, "notes_stats"))
os.makedirs(RESULTS_PATH, exist_ok=True)
def make_output_path(
filename: str,
extension=None,
path=RESULTS_PATH,
) -> str:
return utils.make_output_path(filename=filename, extension=extension, path=path)
def save_figure_as(
fig, filename, formats=("png", "pdf"), directory=RESULTS_PATH, **kwargs
):
if formats is not None:
for fmt in formats:
plotting.write_image(fig, filename, directory, format=fmt, **kwargs)
else:
plotting.write_image(fig, filename, directory, **kwargs)
Loading data
Show source
D = utils.get_dataset("grieg_lyric_pieces", corpus_release="v2.3")
package = D.inputs.get_package()
package_info = package._package.custom
git_tag = package_info.get("git_tag")
utils.print_heading("Data and software versions")
print("Edvard Grieg – Lyric Pieces version v2.3")
print(f"Datapackage '{package.package_name}' @ {git_tag}")
print(f"dimcat version {dc.__version__}\n")
D
Data and software versions
--------------------------
Edvard Grieg – Lyric Pieces version v2.3
Datapackage 'grieg_lyric_pieces' @ v2.3
dimcat version 3.4.0
Dataset
=======
{'inputs': {'basepath': None,
'packages': {'grieg_lyric_pieces': ["'grieg_lyric_pieces.measures' (MuseScoreFacetName.MuseScoreMeasures)",
"'grieg_lyric_pieces.notes' (MuseScoreFacetName.MuseScoreNotes)",
"'grieg_lyric_pieces.expanded' (MuseScoreFacetName.MuseScoreHarmonies)",
"'grieg_lyric_pieces.chords' (MuseScoreFacetName.MuseScoreChords)",
"'grieg_lyric_pieces.metadata' (FeatureName.Metadata)"]}},
'outputs': {'basepath': None, 'packages': {}},
'pipeline': []}
Metadata#
filtered_D = filters.HasHarmonyLabelsFilter(keep_values=[True]).process(D)
all_metadata = filtered_D.get_metadata()
all_metadata.reset_index(level=1).groupby(level=0).nth(0).iloc[:, :20]
piece | TimeSig | KeySig | last_mc | last_mn | length_qb | last_mc_unfolded | last_mn_unfolded | length_qb_unfolded | volta_mcs | all_notes_qb | n_onsets | n_onset_positions | guitar_chord_count | form_label_count | label_count | annotated_key | harmony_version | annotators | reviewers | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
corpus | ||||||||||||||||||||
grieg_lyric_pieces | op12n01 | {1: '2/4'} | {1: -3} | 23 | 23 | 46.0 | 23 | 23 | 46.0 | () | 135.5 | 268 | 156 | 0 | 0 | 43 | Eb | 2.3.0 | Adrian Nagel (2.1.1), John Heilig (2.30) | Adrian Nagel |
chronological_order = utils.chronological_corpus_order(all_metadata)
corpus_colors = dict(zip(chronological_order, utils.CORPUS_COLOR_SCALE))
notes_feature = filtered_D.get_feature("notes")
all_notes = notes_feature.df
print(f"{len(all_notes.index)} notes over {len(all_notes.groupby(level=[0,1]))} files.")
all_notes.head()
65818 notes over 66 files.
mc | mn | quarterbeats | quarterbeats_all_endings | duration_qb | duration | mc_onset | mn_onset | timesig | staff | voice | volta | chord_id | gracenote | midi | name | nominal_duration | octave | scalar | tied | tremolo | tpc_name | tpc | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
corpus | piece | i | |||||||||||||||||||||||
grieg_lyric_pieces | op12n01 | 0 | 1 | 1 | 0 | 0 | 2.00 | 1/2 | 0 | 0 | 2/4 | 2 | 2 | <NA> | 12 | <NA> | 51 | Eb3 | 1/2 | 3 | 1 | 1 | <NA> | Eb | -3 |
1 | 1 | 1 | 0 | 0 | 0.25 | 1/16 | 0 | 0 | 2/4 | 2 | 1 | <NA> | 4 | <NA> | 58 | Bb3 | 1/16 | 3 | 1 | <NA> | <NA> | Bb | -2 | ||
2 | 1 | 1 | 0 | 0 | 0.50 | 1/8 | 0 | 0 | 2/4 | 1 | 1 | <NA> | 0 | <NA> | 79 | G5 | 1/8 | 5 | 1 | <NA> | <NA> | G | 1 | ||
3 | 1 | 1 | 1/4 | 1/4 | 0.25 | 1/16 | 1/16 | 1/16 | 2/4 | 2 | 1 | <NA> | 5 | <NA> | 63 | Eb4 | 1/16 | 4 | 1 | <NA> | <NA> | Eb | -3 | ||
4 | 1 | 1 | 1/2 | 1/2 | 0.25 | 1/16 | 1/8 | 1/8 | 2/4 | 2 | 1 | <NA> | 6 | <NA> | 67 | G4 | 1/16 | 4 | 1 | <NA> | <NA> | G | 1 |
def weight_notes(nl, group_col="midi", precise=True):
summed_durations = nl.groupby(group_col).duration_qb.sum()
shortest_duration = summed_durations[summed_durations > 0].min()
summed_durations /= shortest_duration # normalize such that the shortest duration results in 1 occurrence
if not precise:
# This simple trick reduces compute time but also precision:
# The rationale is to have the smallest value be slightly larger than 0.5 because
# if it was exactly 0.5 it would be rounded down by repeat_notes_according_to_weights()
summed_durations /= 1.9999999
return repeat_notes_according_to_weights(summed_durations)
def repeat_notes_according_to_weights(weights):
try:
counts = weights.round().astype(int)
except Exception:
return pd.Series(dtype=int)
counts_reflecting_weights = []
for pitch, count in counts.items():
counts_reflecting_weights.extend([pitch] * count)
return pd.Series(counts_reflecting_weights)
Ambitus#
corpus_names = {
corp: utils.get_corpus_display_name(corp) for corp in chronological_order
}
chronological_corpus_names = list(corpus_names.values())
corpus_name_colors = {
corpus_names[corp]: color for corp, color in corpus_colors.items()
}
all_notes["corpus_name"] = all_notes.index.get_level_values(0).map(corpus_names)
grouped_notes = all_notes.groupby("corpus_name")
weighted_midi = pd.concat(
[weight_notes(nl, "midi", precise=False) for _, nl in grouped_notes],
keys=grouped_notes.groups.keys(),
).reset_index(level=0)
weighted_midi.columns = ["dataset", "midi"]
weighted_midi
dataset | midi | |
---|---|---|
0 | Grieg Lyric Pieces | 21 |
1 | Grieg Lyric Pieces | 23 |
2 | Grieg Lyric Pieces | 23 |
3 | Grieg Lyric Pieces | 23 |
4 | Grieg Lyric Pieces | 23 |
... | ... | ... |
27852 | Grieg Lyric Pieces | 96 |
27853 | Grieg Lyric Pieces | 96 |
27854 | Grieg Lyric Pieces | 97 |
27855 | Grieg Lyric Pieces | 99 |
27856 | Grieg Lyric Pieces | 102 |
27857 rows × 2 columns
# fig = px.violin(weighted_midi,
# x='dataset',
# y='midi',
# color='dataset',
# title="Corpus-wise distribution over registers (ambitus)",
# box=True,
# labels=dict(
# dataset='',
# midi='distribution of pitches by duration'
# ),
# category_orders=dict(dataset=chronological_corpus_names),
# color_discrete_map=corpus_name_colors,
# width=1000, height=600,
# )
# fig.update_traces(spanmode='hard') # do not extend beyond outliers
# fig.update_layout(**utils.STD_LAYOUT,
# showlegend=False)
# fig.update_yaxes(
# tickmode= 'array',
# tickvals= [12, 24, 36, 48, 60, 72, 84, 96],
# ticktext = ["C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7"],
# )
# fig.update_xaxes(tickangle=45)
# save_figure_as(fig, "ambitus_corpuswise_violins")
# fig.show()
Tonal Pitch Classes (TPC)#
weighted_tpc = pd.concat(
[weight_notes(nl, "tpc") for _, nl in grouped_notes],
keys=grouped_notes.groups.keys(),
).reset_index(level=0)
weighted_tpc.columns = ["dataset", "tpc"]
weighted_tpc
dataset | tpc | |
---|---|---|
0 | Grieg Lyric Pieces | -11 |
1 | Grieg Lyric Pieces | -10 |
2 | Grieg Lyric Pieces | -10 |
3 | Grieg Lyric Pieces | -10 |
4 | Grieg Lyric Pieces | -10 |
... | ... | ... |
74268 | Grieg Lyric Pieces | 16 |
74269 | Grieg Lyric Pieces | 16 |
74270 | Grieg Lyric Pieces | 16 |
74271 | Grieg Lyric Pieces | 16 |
74272 | Grieg Lyric Pieces | 16 |
74273 rows × 2 columns
As violin plot#
# fig = px.violin(weighted_tpc,
# x='dataset',
# y='tpc',
# color='dataset',
# title="Corpus-wise distribution over line of fifths (tonal pitch classes)",
# box=True,
# labels=dict(
# dataset='',
# tpc='distribution of tonal pitch classes by duration'
# ),
# category_orders=dict(dataset=chronological_corpus_names),
# color_discrete_map=corpus_name_colors,
# width=1000,
# height=600,
# )
# fig.update_traces(spanmode='hard') # do not extend beyond outliers
# fig.update_layout(**utils.STD_LAYOUT,
# showlegend=False)
# fig.update_yaxes(
# tickmode= 'array',
# tickvals= [-12, -9, -6, -3, 0, 3, 6, 9, 12, 15, 18],
# ticktext = ["Dbb", "Bbb", "Gb", "Eb", "C", "A", "F#", "D#", "B#", "G##", "E##"],
# zerolinecolor='grey',
# zeroline=True
# )
# fig.update_xaxes(tickangle=45)
# save_figure_as(fig, "pitch_class_distributions_corpuswise_violins")
# fig.show()
(all_notes)
mc | mn | quarterbeats | quarterbeats_all_endings | duration_qb | duration | mc_onset | mn_onset | timesig | staff | voice | volta | chord_id | gracenote | midi | name | nominal_duration | octave | scalar | tied | tremolo | tpc_name | tpc | corpus_name | |||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
corpus | piece | i | ||||||||||||||||||||||||
grieg_lyric_pieces | op12n01 | 0 | 1 | 1 | 0 | 0 | 2.00 | 1/2 | 0 | 0 | 2/4 | 2 | 2 | <NA> | 12 | <NA> | 51 | Eb3 | 1/2 | 3 | 1 | 1 | <NA> | Eb | -3 | Grieg Lyric Pieces |
1 | 1 | 1 | 0 | 0 | 0.25 | 1/16 | 0 | 0 | 2/4 | 2 | 1 | <NA> | 4 | <NA> | 58 | Bb3 | 1/16 | 3 | 1 | <NA> | <NA> | Bb | -2 | Grieg Lyric Pieces | ||
2 | 1 | 1 | 0 | 0 | 0.50 | 1/8 | 0 | 0 | 2/4 | 1 | 1 | <NA> | 0 | <NA> | 79 | G5 | 1/8 | 5 | 1 | <NA> | <NA> | G | 1 | Grieg Lyric Pieces | ||
3 | 1 | 1 | 1/4 | 1/4 | 0.25 | 1/16 | 1/16 | 1/16 | 2/4 | 2 | 1 | <NA> | 5 | <NA> | 63 | Eb4 | 1/16 | 4 | 1 | <NA> | <NA> | Eb | -3 | Grieg Lyric Pieces | ||
4 | 1 | 1 | 1/2 | 1/2 | 0.25 | 1/16 | 1/8 | 1/8 | 2/4 | 2 | 1 | <NA> | 6 | <NA> | 67 | G4 | 1/16 | 4 | 1 | <NA> | <NA> | G | 1 | Grieg Lyric Pieces | ||
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | |
op71n07 | 729 | 75 | 74 | 222 | 222 | 1.00 | 1/4 | 1/2 | 1/2 | 3/4 | 2 | 1 | <NA> | 509 | <NA> | 58 | Bb3 | 1/4 | 3 | 1 | <NA> | <NA> | Bb | -2 | Grieg Lyric Pieces | |
730 | 75 | 74 | 222 | 222 | 1.00 | 1/4 | 1/2 | 1/2 | 3/4 | 2 | 1 | <NA> | 509 | <NA> | 63 | Eb4 | 1/4 | 4 | 1 | <NA> | <NA> | Eb | -3 | Grieg Lyric Pieces | ||
731 | 75 | 74 | 222 | 222 | 1.00 | 1/4 | 1/2 | 1/2 | 3/4 | 1 | 2 | <NA> | 506 | <NA> | 67 | G4 | 1/4 | 4 | 1 | <NA> | <NA> | G | 1 | Grieg Lyric Pieces | ||
732 | 75 | 74 | 222 | 222 | 1.00 | 1/4 | 1/2 | 1/2 | 3/4 | 1 | 2 | <NA> | 506 | <NA> | 70 | Bb4 | 1/4 | 4 | 1 | <NA> | <NA> | Bb | -2 | Grieg Lyric Pieces | ||
733 | 75 | 74 | 445/2 | 445/2 | 0.50 | 1/8 | 5/8 | 5/8 | 3/4 | 1 | 1 | <NA> | 504 | <NA> | 79 | G5 | 1/8 | 5 | 1 | <NA> | <NA> | G | 1 | Grieg Lyric Pieces |
65818 rows × 24 columns
width = 1400
height = 800
weighted_pitch_values = pd.concat(
[
weighted_midi.rename(columns={"midi": "value"}),
weighted_tpc.rename(columns={"tpc": "value"}),
],
keys=["MIDI pitch", "Tonal pitch class"],
names=["distribution"],
).reset_index(level=[0, 1])
fig = plotting.make_violin_plot(
weighted_pitch_values,
x_col="dataset",
y_col="value",
color="dataset",
facet_row="distribution",
box=True,
labels=dict(dataset="", tpc="distribution of tonal pitch classes by duration"),
category_orders=dict(dataset=chronological_corpus_names),
# color_discrete_map=corpus_name_colors,
color_discrete_sequence=px.colors.qualitative.Dark24,
traces_settings=dict(
spanmode="hard",
width=1,
# scalemode='width'
),
layout=dict(
showlegend=False,
margin=dict(
t=0,
b=0,
l=0,
r=0,
),
),
x_axis=dict(
# tickangle=45,
tickfont_size=15
),
y_axis=dict(
tickmode="array",
tickvals=[-12, -9, -6, -3, 0, 3, 6, 9, 12, 15, 24, 36, 48, 60, 72, 84, 96],
ticktext=[
"Dbb",
"Bbb",
"Gb",
"Eb",
"C",
"A",
"F#",
"D#",
"B#",
"G##",
"C1",
"C2",
"C3",
"C4",
"C5",
"C6",
"C7",
],
zerolinecolor="grey",
zeroline=True,
),
width=width,
height=height,
)
utils.realign_subplot_axes(fig, y_axes=dict(title_text=""))
save_figure_as(fig, "notes_violin", width=width, height=height)
fig