Following Section 1.2 of [Müller, FMP, Springer 2015], we introduce in this notebook at a simple symbolic music representation encoded in some comma-separated values (CSV) format.
Recall that a piano-roll representation yields a visualization of note events, where each note is encoded by a start, duration, and pitch parameter. Further parameters could encode a note's velocity, articulation, or instrumentation. In the following, we introduce a simple format stored in a comma-separated values (CSV) file. Each note event corresponds to a line encoding a data record consisting of the four parameters start
, duration
, pitch
, velocity
, and label
. The first four parameters are real numbers and encode the start (given in seconds or measures), duration (given in seconds or measures), pitch (encoded as MIDI note number), and velocity (in the range between 0.0 and 1.0) of a note event. The last parameter label
is a string that my encode further information such as the instrument (e.g. violin, trumpet, flute), the voice (e.g. soprano, alto, tenor and bass), or some staff information (e.g. left/right hand for a piano score). In the following, we use the first five measures of Symphony No. 5 by Ludwig van Beethoven in a piano reduced version as a running example.
A CSV file of a piano-roll representation of this score looks as follows:
import os
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import patches
import pandas as pd
import IPython.display as ipd
%matplotlib inline
fn = os.path.join('..', 'data', 'C1', 'FMP_C1_F01_Beethoven_FateMotive_Sibelius.csv')
with open(fn, 'r', encoding='utf-8') as file:
csv_str = file.read()
print(csv_str)
pandas
¶The Python library pandas
provides easy-to-use data structures and data analysis tools. In particular, it can be used to easily read and modify CSV files. When reading a CSV file, the content is stored in a two-dimensional tabular data structure with labeled axes (rows and columns) called DataFrame
. This data structure can be accessed using attributes, see the documentation for pandas.DataFrame
. We apply pandas
to our previous example and print the first four rows.
df = pd.read_csv(fn, sep=';')
print(df.loc[0:5,:])
The pandas
-library allows for rendering a DataFrame
as an HTML table as shown in the next example.
html = df.loc[0:5,:].to_html(index=False)
ipd.HTML(html)
The note events stored in the CSV file can be visualized using a piano-roll representation. The following functions illustrates how this may be done. Note that the color of a rectangle is used to indicated the different labels associated to note events.
import sys
sys.path.append('..')
import libfmp.b
def csv_to_list(csv):
"""Convert a csv score file to a list of note events
Notebook: C1/C1S2_CSV.ipynb
Args:
csv (str or pd.DataFrame): Either a path to a csv file or a data frame
Returns:
score (list): A list of note events where each note is specified as
``[start, duration, pitch, velocity, label]``
"""
if isinstance(csv, str):
df = libfmp.b.read_csv(csv)
elif isinstance(csv, pd.DataFrame):
df = csv
else:
raise RuntimeError('csv must be a path to a csv file or pd.DataFrame')
score = []
for i, (start, duration, pitch, velocity, label) in df.iterrows():
score.append([start, duration, pitch, velocity, label])
return score
def visualize_piano_roll(score, xlabel='Time (seconds)', ylabel='Pitch', colors='FMP_1', velocity_alpha=False,
figsize=(12, 4), ax=None, dpi=72):
"""Plot a pianoroll visualization
Notebook: C1/C1S2_CSV.ipynb
Args:
score: List of note events
xlabel: Label for x axis (Default value = 'Time (seconds)')
ylabel: Label for y axis (Default value = 'Pitch')
colors: Several options: 1. string of FMP_COLORMAPS, 2. string of matplotlib colormap,
3. list or np.ndarray of matplotlib color specifications,
4. dict that assigns labels to colors (Default value = 'FMP_1')
velocity_alpha: Use the velocity value for the alpha value of the corresponding rectangle
(Default value = False)
figsize: Width, height in inches (Default value = (12)
ax: The Axes instance to plot on (Default value = None)
dpi: Dots per inch (Default value = 72)
Returns:
fig: The created matplotlib figure or None if ax was given.
ax: The used axes
"""
fig = None
if ax is None:
fig = plt.figure(figsize=figsize, dpi=dpi)
ax = plt.subplot(1, 1, 1)
labels_set = sorted(set([note[4] for note in score]))
colors = libfmp.b.color_argument_to_dict(colors, labels_set)
pitch_min = min(note[2] for note in score)
pitch_max = max(note[2] for note in score)
time_min = min(note[0] for note in score)
time_max = max(note[0] + note[1] for note in score)
for start, duration, pitch, velocity, label in score:
if velocity_alpha is False:
velocity = None
rect = patches.Rectangle((start, pitch - 0.5), duration, 1, linewidth=1,
edgecolor='k', facecolor=colors[label], alpha=velocity)
ax.add_patch(rect)
ax.set_ylim([pitch_min - 1.5, pitch_max + 1.5])
ax.set_xlim([min(time_min, 0), time_max + 0.5])
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.grid()
ax.set_axisbelow(True)
ax.legend([patches.Patch(linewidth=1, edgecolor='k', facecolor=colors[key]) for key in labels_set],
labels_set, loc='upper right', framealpha=1)
if fig is not None:
plt.tight_layout()
return fig, ax
score = csv_to_list(df)
visualize_piano_roll(score, colors=['red','blue'], figsize=(8, 3));
The next example shows a piano-roll representation for a full orchestral score of the first 21 measures of Beethoven's Fifth, see the notebook on sheet music representations. Note that note events that overlap in the time–pitch domain are painted over by the visualization function in a greedy fashion.
fn = os.path.join('..', 'data', 'C1', 'FMP_C1_F10_Beethoven_Fifth-MM1-21_Sibelius-Orchestra.csv')
df = pd.read_csv(fn, sep=';')
score_list = csv_to_list(df)
visualize_piano_roll(score_list, figsize=(10, 7), colors='gist_rainbow');
The next example shows a piano-roll representation of the beginning of the four-voice Fugue BWV 846 in C major by Johann Sebastian Bach. The four different voices are labeled with 'Soprano', 'Alto', 'Tenor', and 'Basso'.
fn = os.path.join('..', 'data', 'C1', 'FMP_C1_F12_Bach_BWV846_Sibelius-Tracks.csv')
df = pd.read_csv(fn, sep=';')
score_list = csv_to_list(df)
visualize_piano_roll(score_list, figsize=(8, 3));
#output_path_filename = os.path.join('..', 'output', 'C1', 'FMP_C1_F12_Bach_BWV846_PianoRoll.png')
#plt.savefig(output_path_filename)
The functions to convert a csv file to a list of note events as well as the pianoroll visualiztion function have been included into libfmp. In the following code cell, we call those libfmp
functions:
import sys
sys.path.append('..')
import libfmp.c1
fn = os.path.join('..', 'data', 'C1', 'FMP_C1_F01_Beethoven_FateMotive_Sibelius.csv')
score = libfmp.c1.csv_to_list(fn)
libfmp.c1.visualize_piano_roll(score, figsize=(8, 3));