Source code for libfmp.c1.c1s2_symbolic_rep

"""
Module: libfmp.c1.c1s2_symbolic_rep
Author: Frank Zalkow, Meinard Müller
License: The MIT license, https://opensource.org/licenses/MIT

This file is part of the FMP Notebooks (https://www.audiolabs-erlangen.de/FMP)
"""
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib import patches
import pretty_midi
import music21 as m21

import libfmp.b


[docs]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
[docs]def midi_to_list(midi): """Convert a midi file to a list of note events Notebook: C1/C1S2_MIDI.ipynb Args: midi (str or pretty_midi.pretty_midi.PrettyMIDI): Either a path to a midi file or PrettyMIDI object Returns: score (list): A list of note events where each note is specified as ``[start, duration, pitch, velocity, label]`` """ if isinstance(midi, str): midi_data = pretty_midi.pretty_midi.PrettyMIDI(midi) elif isinstance(midi, pretty_midi.pretty_midi.PrettyMIDI): midi_data = midi else: raise RuntimeError('midi must be a path to a midi file or pretty_midi.PrettyMIDI') score = [] for instrument in midi_data.instruments: for note in instrument.notes: start = note.start duration = note.end - start pitch = note.pitch velocity = note.velocity / 127. # since midi velocity is in [0, 127] score.append([start, duration, pitch, velocity, instrument.name]) return score
[docs]def xml_to_list(xml): """Convert a music xml file to a list of note events Notebook: C1/C1S2_MusicXML.ipynb Args: xml (str or music21.stream.Score): Either a path to a music xml file or a music21.stream.Score Returns: score (list): A list of note events where each note is specified as ``[start, duration, pitch, velocity, label]`` """ if isinstance(xml, str): xml_data = m21.converter.parse(xml) elif isinstance(xml, m21.stream.Score): xml_data = xml else: raise RuntimeError('midi must be a path to a midi file or music21.stream.Score') score = [] for part in xml_data.parts: instrument = part.getInstrument().instrumentName for note in part.flat.notes: if note.isChord: start = note.offset duration = note.quarterLength for chord_note in note.pitches: pitch = chord_note.ps volume = note.volume.realized score.append([start, duration, pitch, volume, instrument]) else: start = note.offset duration = note.quarterLength pitch = note.pitch.ps volume = note.volume.realized score.append([start, duration, pitch, volume, instrument]) score = sorted(score, key=lambda x: (x[0], x[2])) return score
[docs]def list_to_csv(score, fn_out): """Write a list of note events (comprising a start time, duration, pitch, velocity, and label for each note event) to a CSV file Args: score (list): List of note events fn_out (str): The path of the csv file to be created """ df = pd.DataFrame(score, columns=['Start', 'Duration', 'Pitch', 'Velocity', 'Instrument']) # ideally, I would like to use float_format='%.3f', but then the numeric columns are considered as strings and, # therefore, are quoted df.to_csv(fn_out, sep=';', index=False, quoting=2)
[docs]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