Notes#

Hide 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)
Hide 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

Hide source
D = utils.get_dataset("couperin_clavecin", corpus_release="v2.4")
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("François Couperin – L'art de toucher le clavecin version v2.4")
print(f"Datapackage '{package.package_name}' @ {git_tag}")
print(f"dimcat version {dc.__version__}\n")
D
Data and software versions
--------------------------

François Couperin – L'art de toucher le clavecin version v2.4
Datapackage 'couperin_clavecin' @ v2.4
dimcat version 3.4.0
Dataset
=======
{'inputs': {'basepath': None,
            'packages': {'couperin_clavecin': ["'couperin_clavecin.measures' (MuseScoreFacetName.MuseScoreMeasures)",
                                               "'couperin_clavecin.notes' (MuseScoreFacetName.MuseScoreNotes)",
                                               "'couperin_clavecin.expanded' (MuseScoreFacetName.MuseScoreHarmonies)",
                                               "'couperin_clavecin.chords' (MuseScoreFacetName.MuseScoreChords)",
                                               "'couperin_clavecin.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
couperin_clavecin 00_allemande {1: '4/4'} {1: -1} 15 13 52.0 30 26 104.0 () 101.75 322 210 0 0 66 d 2.3.0 Adrian Nagel (2.1.0), Davor Krkljus (2.3.0) DK, Hanné Becker
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()
4012 notes over 9 files.
mc mn quarterbeats quarterbeats_all_endings duration_qb duration mc_onset mn_onset timesig staff voice chord_id gracenote midi name nominal_duration octave scalar tied tpc_name tpc
corpus piece i
couperin_clavecin 00_allemande 0 1 0 0 0 0.25 1/16 0 5/16 4/4 1 1 0 <NA> 69 A4 1/16 4 1 <NA> A 3
1 1 0 1/4 1/4 0.25 1/16 1/16 3/8 4/4 1 1 1 <NA> 71 B4 1/16 4 1 <NA> B 5
2 1 0 1/2 1/2 0.25 1/16 1/8 7/16 4/4 1 1 2 <NA> 73 C#5 1/16 5 1 <NA> C# 7
3 1 0 3/4 3/4 0.25 1/16 3/16 1/2 4/4 1 1 3 <NA> 74 D5 1/16 5 1 <NA> D 2
4 1 0 1 1 0.25 1/16 1/4 9/16 4/4 1 1 4 <NA> 69 A4 1/16 4 1 <NA> A 3
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 Couperin Clavecin 31
1 Couperin Clavecin 31
2 Couperin Clavecin 31
3 Couperin Clavecin 31
4 Couperin Clavecin 31
... ... ...
4827 Couperin Clavecin 85
4828 Couperin Clavecin 85
4829 Couperin Clavecin 85
4830 Couperin Clavecin 86
4831 Couperin Clavecin 86

4832 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 Couperin Clavecin -4
1 Couperin Clavecin -3
2 Couperin Clavecin -3
3 Couperin Clavecin -3
4 Couperin Clavecin -3
5 Couperin Clavecin -3
6 Couperin Clavecin -3
7 Couperin Clavecin -2
8 Couperin Clavecin -2
9 Couperin Clavecin -2
10 Couperin Clavecin -2
11 Couperin Clavecin -2
12 Couperin Clavecin -2
13 Couperin Clavecin -2
14 Couperin Clavecin -2
15 Couperin Clavecin -2
16 Couperin Clavecin -2
17 Couperin Clavecin -2
18 Couperin Clavecin -2
19 Couperin Clavecin -2
20 Couperin Clavecin -2
21 Couperin Clavecin -2
22 Couperin Clavecin -2
23 Couperin Clavecin -2
24 Couperin Clavecin -2
25 Couperin Clavecin -2
26 Couperin Clavecin -1
27 Couperin Clavecin -1
28 Couperin Clavecin -1
29 Couperin Clavecin -1
30 Couperin Clavecin -1
31 Couperin Clavecin -1
32 Couperin Clavecin -1
33 Couperin Clavecin -1
34 Couperin Clavecin -1
35 Couperin Clavecin -1
36 Couperin Clavecin -1
37 Couperin Clavecin -1
38 Couperin Clavecin -1
39 Couperin Clavecin -1
40 Couperin Clavecin -1
41 Couperin Clavecin -1
42 Couperin Clavecin -1
43 Couperin Clavecin -1
44 Couperin Clavecin -1
45 Couperin Clavecin -1
46 Couperin Clavecin -1
47 Couperin Clavecin -1
48 Couperin Clavecin -1
49 Couperin Clavecin -1
50 Couperin Clavecin -1
51 Couperin Clavecin -1
52 Couperin Clavecin -1
53 Couperin Clavecin -1
54 Couperin Clavecin -1
55 Couperin Clavecin -1
56 Couperin Clavecin -1
57 Couperin Clavecin -1
58 Couperin Clavecin -1
59 Couperin Clavecin -1
60 Couperin Clavecin 0
61 Couperin Clavecin 0
62 Couperin Clavecin 0
63 Couperin Clavecin 0
64 Couperin Clavecin 0
65 Couperin Clavecin 0
66 Couperin Clavecin 0
67 Couperin Clavecin 0
68 Couperin Clavecin 0
69 Couperin Clavecin 0
70 Couperin Clavecin 0
71 Couperin Clavecin 0
72 Couperin Clavecin 0
73 Couperin Clavecin 0
74 Couperin Clavecin 0
75 Couperin Clavecin 0
76 Couperin Clavecin 0
77 Couperin Clavecin 0
78 Couperin Clavecin 0
79 Couperin Clavecin 0
80 Couperin Clavecin 0
81 Couperin Clavecin 0
82 Couperin Clavecin 0
83 Couperin Clavecin 0
84 Couperin Clavecin 0
85 Couperin Clavecin 0
86 Couperin Clavecin 0
87 Couperin Clavecin 0
88 Couperin Clavecin 0
89 Couperin Clavecin 0
90 Couperin Clavecin 0
91 Couperin Clavecin 0
92 Couperin Clavecin 0
93 Couperin Clavecin 1
94 Couperin Clavecin 1
95 Couperin Clavecin 1
96 Couperin Clavecin 1
97 Couperin Clavecin 1
98 Couperin Clavecin 1
99 Couperin Clavecin 1
100 Couperin Clavecin 1
101 Couperin Clavecin 1
102 Couperin Clavecin 1
103 Couperin Clavecin 1
104 Couperin Clavecin 1
105 Couperin Clavecin 1
106 Couperin Clavecin 1
107 Couperin Clavecin 1
108 Couperin Clavecin 1
109 Couperin Clavecin 1
110 Couperin Clavecin 1
111 Couperin Clavecin 1
112 Couperin Clavecin 1
113 Couperin Clavecin 1
114 Couperin Clavecin 1
115 Couperin Clavecin 1
116 Couperin Clavecin 1
117 Couperin Clavecin 1
118 Couperin Clavecin 1
119 Couperin Clavecin 1
120 Couperin Clavecin 1
121 Couperin Clavecin 1
122 Couperin Clavecin 1
123 Couperin Clavecin 1
124 Couperin Clavecin 1
125 Couperin Clavecin 1
126 Couperin Clavecin 1
127 Couperin Clavecin 1
128 Couperin Clavecin 1
129 Couperin Clavecin 1
130 Couperin Clavecin 1
131 Couperin Clavecin 1
132 Couperin Clavecin 1
133 Couperin Clavecin 1
134 Couperin Clavecin 1
135 Couperin Clavecin 1
136 Couperin Clavecin 1
137 Couperin Clavecin 1
138 Couperin Clavecin 1
139 Couperin Clavecin 2
140 Couperin Clavecin 2
141 Couperin Clavecin 2
142 Couperin Clavecin 2
143 Couperin Clavecin 2
144 Couperin Clavecin 2
145 Couperin Clavecin 2
146 Couperin Clavecin 2
147 Couperin Clavecin 2
148 Couperin Clavecin 2
149 Couperin Clavecin 2
150 Couperin Clavecin 2
151 Couperin Clavecin 2
152 Couperin Clavecin 2
153 Couperin Clavecin 2
154 Couperin Clavecin 2
155 Couperin Clavecin 2
156 Couperin Clavecin 2
157 Couperin Clavecin 2
158 Couperin Clavecin 2
159 Couperin Clavecin 2
160 Couperin Clavecin 2
161 Couperin Clavecin 2
162 Couperin Clavecin 2
163 Couperin Clavecin 2
164 Couperin Clavecin 2
165 Couperin Clavecin 2
166 Couperin Clavecin 2
167 Couperin Clavecin 2
168 Couperin Clavecin 2
169 Couperin Clavecin 2
170 Couperin Clavecin 2
171 Couperin Clavecin 2
172 Couperin Clavecin 2
173 Couperin Clavecin 2
174 Couperin Clavecin 2
175 Couperin Clavecin 2
176 Couperin Clavecin 2
177 Couperin Clavecin 2
178 Couperin Clavecin 2
179 Couperin Clavecin 2
180 Couperin Clavecin 2
181 Couperin Clavecin 2
182 Couperin Clavecin 2
183 Couperin Clavecin 2
184 Couperin Clavecin 2
185 Couperin Clavecin 2
186 Couperin Clavecin 3
187 Couperin Clavecin 3
188 Couperin Clavecin 3
189 Couperin Clavecin 3
190 Couperin Clavecin 3
191 Couperin Clavecin 3
192 Couperin Clavecin 3
193 Couperin Clavecin 3
194 Couperin Clavecin 3
195 Couperin Clavecin 3
196 Couperin Clavecin 3
197 Couperin Clavecin 3
198 Couperin Clavecin 3
199 Couperin Clavecin 3
200 Couperin Clavecin 3
201 Couperin Clavecin 3
202 Couperin Clavecin 3
203 Couperin Clavecin 3
204 Couperin Clavecin 3
205 Couperin Clavecin 3
206 Couperin Clavecin 3
207 Couperin Clavecin 3
208 Couperin Clavecin 3
209 Couperin Clavecin 3
210 Couperin Clavecin 3
211 Couperin Clavecin 3
212 Couperin Clavecin 3
213 Couperin Clavecin 3
214 Couperin Clavecin 3
215 Couperin Clavecin 3
216 Couperin Clavecin 3
217 Couperin Clavecin 3
218 Couperin Clavecin 3
219 Couperin Clavecin 3
220 Couperin Clavecin 3
221 Couperin Clavecin 3
222 Couperin Clavecin 3
223 Couperin Clavecin 3
224 Couperin Clavecin 3
225 Couperin Clavecin 3
226 Couperin Clavecin 3
227 Couperin Clavecin 3
228 Couperin Clavecin 3
229 Couperin Clavecin 3
230 Couperin Clavecin 4
231 Couperin Clavecin 4
232 Couperin Clavecin 4
233 Couperin Clavecin 4
234 Couperin Clavecin 4
235 Couperin Clavecin 4
236 Couperin Clavecin 4
237 Couperin Clavecin 4
238 Couperin Clavecin 4
239 Couperin Clavecin 4
240 Couperin Clavecin 4
241 Couperin Clavecin 4
242 Couperin Clavecin 4
243 Couperin Clavecin 4
244 Couperin Clavecin 4
245 Couperin Clavecin 4
246 Couperin Clavecin 4
247 Couperin Clavecin 4
248 Couperin Clavecin 4
249 Couperin Clavecin 4
250 Couperin Clavecin 4
251 Couperin Clavecin 4
252 Couperin Clavecin 4
253 Couperin Clavecin 4
254 Couperin Clavecin 4
255 Couperin Clavecin 4
256 Couperin Clavecin 4
257 Couperin Clavecin 4
258 Couperin Clavecin 4
259 Couperin Clavecin 4
260 Couperin Clavecin 4
261 Couperin Clavecin 4
262 Couperin Clavecin 4
263 Couperin Clavecin 4
264 Couperin Clavecin 4
265 Couperin Clavecin 4
266 Couperin Clavecin 4
267 Couperin Clavecin 4
268 Couperin Clavecin 5
269 Couperin Clavecin 5
270 Couperin Clavecin 5
271 Couperin Clavecin 5
272 Couperin Clavecin 5
273 Couperin Clavecin 5
274 Couperin Clavecin 5
275 Couperin Clavecin 5
276 Couperin Clavecin 5
277 Couperin Clavecin 5
278 Couperin Clavecin 5
279 Couperin Clavecin 5
280 Couperin Clavecin 5
281 Couperin Clavecin 5
282 Couperin Clavecin 5
283 Couperin Clavecin 5
284 Couperin Clavecin 5
285 Couperin Clavecin 5
286 Couperin Clavecin 5
287 Couperin Clavecin 5
288 Couperin Clavecin 5
289 Couperin Clavecin 5
290 Couperin Clavecin 5
291 Couperin Clavecin 5
292 Couperin Clavecin 5
293 Couperin Clavecin 5
294 Couperin Clavecin 5
295 Couperin Clavecin 5
296 Couperin Clavecin 5
297 Couperin Clavecin 5
298 Couperin Clavecin 6
299 Couperin Clavecin 6
300 Couperin Clavecin 6
301 Couperin Clavecin 6
302 Couperin Clavecin 6
303 Couperin Clavecin 6
304 Couperin Clavecin 6
305 Couperin Clavecin 6
306 Couperin Clavecin 6
307 Couperin Clavecin 6
308 Couperin Clavecin 6
309 Couperin Clavecin 6
310 Couperin Clavecin 6
311 Couperin Clavecin 6
312 Couperin Clavecin 6
313 Couperin Clavecin 6
314 Couperin Clavecin 6
315 Couperin Clavecin 6
316 Couperin Clavecin 6
317 Couperin Clavecin 6
318 Couperin Clavecin 7
319 Couperin Clavecin 7
320 Couperin Clavecin 7
321 Couperin Clavecin 7
322 Couperin Clavecin 7
323 Couperin Clavecin 7
324 Couperin Clavecin 7
325 Couperin Clavecin 7
326 Couperin Clavecin 7
327 Couperin Clavecin 7
328 Couperin Clavecin 7
329 Couperin Clavecin 7
330 Couperin Clavecin 7
331 Couperin Clavecin 7
332 Couperin Clavecin 7
333 Couperin Clavecin 8
334 Couperin Clavecin 8
335 Couperin Clavecin 8
336 Couperin Clavecin 8
337 Couperin Clavecin 8
338 Couperin Clavecin 8
339 Couperin Clavecin 9
340 Couperin Clavecin 9
341 Couperin Clavecin 9
342 Couperin Clavecin 10
343 Couperin Clavecin 10
344 Couperin Clavecin 11

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 chord_id gracenote midi name nominal_duration octave scalar tied tpc_name tpc corpus_name
corpus piece i
couperin_clavecin 00_allemande 0 1 0 0 0 0.25 1/16 0 5/16 4/4 1 1 0 <NA> 69 A4 1/16 4 1 <NA> A 3 Couperin Clavecin
1 1 0 1/4 1/4 0.25 1/16 1/16 3/8 4/4 1 1 1 <NA> 71 B4 1/16 4 1 <NA> B 5 Couperin Clavecin
2 1 0 1/2 1/2 0.25 1/16 1/8 7/16 4/4 1 1 2 <NA> 73 C#5 1/16 5 1 <NA> C# 7 Couperin Clavecin
3 1 0 3/4 3/4 0.25 1/16 3/16 1/2 4/4 1 1 3 <NA> 74 D5 1/16 5 1 <NA> D 2 Couperin Clavecin
4 1 0 1 1 0.25 1/16 1/4 9/16 4/4 1 1 4 <NA> 69 A4 1/16 4 1 <NA> A 3 Couperin Clavecin
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
08_huitieme_prelude 607 31 31 183/2 183/2 1.50 3/8 3/8 3/8 6/8 2 2 610 <NA> 40 E2 1/4 2 3/2 -1 E 4 Couperin Clavecin
608 31 31 183/2 183/2 1.50 3/8 3/8 3/8 6/8 2 1 608 <NA> 52 E3 1/4 3 3/2 <NA> E 4 Couperin Clavecin
609 31 31 183/2 183/2 1.50 3/8 3/8 3/8 6/8 1 2 602 <NA> 55 G3 1/4 3 3/2 -1 G 1 Couperin Clavecin
610 31 31 183/2 183/2 1.50 3/8 3/8 3/8 6/8 1 3 606 <NA> 59 B3 1/4 3 3/2 -1 B 5 Couperin Clavecin
611 31 31 183/2 183/2 1.50 3/8 3/8 3/8 6/8 1 1 598 <NA> 64 E4 1/4 4 3/2 -1 E 4 Couperin Clavecin

4012 rows × 22 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
fig = plotting.make_box_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.Light24,
    # traces_settings=dict(spanmode='hard'),
    layout=dict(showlegend=False, margin=dict(t=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=True)
save_figure_as(fig, "notes_box", width=width, height=height)
fig

As bar plots#

bar_data = all_notes.groupby("tpc").duration_qb.sum().reset_index()
x_values = list(range(bar_data.tpc.min(), bar_data.tpc.max() + 1))
x_names = ms3.fifths2name(x_values)
fig = px.bar(
    bar_data,
    x="tpc",
    y="duration_qb",
    labels=dict(tpc="Named pitch class", duration_qb="Duration in quarter notes"),
    color_discrete_sequence=utils.CORPUS_COLOR_SCALE,
    width=1000,
    height=300,
)
fig.update_layout(**utils.STD_LAYOUT)
fig.update_xaxes(
    zerolinecolor="grey",
    tickmode="array",
    tickvals=x_values,
    ticktext=x_names,
    dtick=1,
    ticks="outside",
    tickcolor="black",
    minor=dict(dtick=6, gridcolor="grey", showgrid=True),
)
save_figure_as(fig, "pitch_class_distribution_absolute_bars")
fig.show()
scatter_data = all_notes.groupby(["corpus_name", "tpc"]).duration_qb.sum().reset_index()
fig = px.bar(
    scatter_data,
    x="tpc",
    y="duration_qb",
    color="corpus_name",
    labels=dict(
        duration_qb="duration",
        tpc="named pitch class",
    ),
    category_orders=dict(dataset=chronological_corpus_names),
    color_discrete_map=corpus_name_colors,
    width=1000,
    height=500,
)
fig.update_layout(**utils.STD_LAYOUT)
fig.update_xaxes(
    zerolinecolor="grey",
    tickmode="array",
    tickvals=x_values,
    ticktext=x_names,
    dtick=1,
    ticks="outside",
    tickcolor="black",
    minor=dict(dtick=6, gridcolor="grey", showgrid=True),
)
save_figure_as(fig, "pitch_class_distribution_corpuswise_absolute_bars")
fig.show()

As scatter plots#

fig = px.scatter(
    scatter_data,
    x="tpc",
    y="duration_qb",
    color="corpus_name",
    labels=dict(
        duration_qb="duration",
        tpc="named pitch class",
    ),
    category_orders=dict(dataset=chronological_corpus_names),
    color_discrete_map=corpus_name_colors,
    facet_col="corpus_name",
    facet_col_wrap=3,
    facet_col_spacing=0.03,
    width=1000,
    height=1000,
)
fig.update_traces(mode="lines+markers")
fig.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1]))
fig.update_layout(**utils.STD_LAYOUT, showlegend=False)
fig.update_xaxes(
    zerolinecolor="grey",
    tickmode="array",
    tickvals=[-12, -6, 0, 6, 12, 18],
    ticktext=["Dbb", "Gb", "C", "F#", "B#", "E##"],
    visible=True,
)
fig.update_yaxes(zeroline=False, matches=None, showticklabels=True)
save_figure_as(fig, "pitch_class_distribution_corpuswise_scatter")
fig.show()
no_accidental = bar_data[bar_data.tpc.between(-1, 5)].duration_qb.sum()
with_accidental = bar_data[~bar_data.tpc.between(-1, 5)].duration_qb.sum()
entire = no_accidental + with_accidental
(
    f"Fraction of note duration without accidental of the entire durations: {no_accidental} / {entire} = "
    f"{no_accidental / entire}"
)
'Fraction of note duration without accidental of the entire durations: 1895.8208333333334 / 2413.541666666667 = 0.7854933103150625'

Notes and staves#

print("Distribution of notes over staves:")
utils.value_count_df(all_notes.staff)
Distribution of notes over staves:
counts %
staff
1 2589 64.53
2 1423 35.47