From bd7463256a777853617b8107dc3e3db2314d1f0e Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 25 May 2022 19:15:04 -0400 Subject: [PATCH 001/122] note_array to part and helper functions. --- partitura/utils/music.py | 221 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index f3f3a3ad..2a896d00 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -6,6 +6,20 @@ from scipy.interpolate import interp1d from scipy.sparse import csc_matrix +# imports for note_array 2 part +from partitura.score import Part, PartGroup +from typing import Union +import numpy.lib.recfunctions as rfn +from fractions import Fraction +import partitura.musicanalysis as analysis +import partitura.score as score +from partitura.utils import ( + estimate_symbolic_duration, + key_name_to_fifths_mode, + estimate_clef_properties, +) + + from partitura.utils.generic import find_nearest, search, iter_current_next MIDI_BASE_CLASS = {"c": 0, "d": 2, "e": 4, "f": 5, "g": 7, "a": 9, "b": 11} @@ -2189,6 +2203,213 @@ def get_matched_notes(spart_note_array, ppart_note_array, alignment): return np.array(matched_idxs) + +def create_divs_from_beats(note_array): + duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] + onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] + divs = np.lcm.reduce( + [Fraction(ix).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) + onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) + duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) + na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) + return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) + + + +def create_part( + ticks, + note_array, + key_sigs, + part_id=None, + part_name=None, +): + warnings.warn("create_part", stacklevel=2) + + part = score.Part(part_id, part_name=part_name) + part.set_quarter_duration(0, ticks) + + clef = score.Clef( + number=1, **estimate_clef_properties(note_array["pitch"]) + ) + part.add(clef, 0) + for t, name in key_sigs: + fifths, mode = key_name_to_fifths_mode(name) + part.add(score.KeySignature(fifths, mode), t) + + warnings.warn("add notes", stacklevel=2) + + for n in note_array: + if n["duration_div"] > 0: + note = score.Note( + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=estimate_symbolic_duration(n["duration_div"], ticks) + ) + else: + note = score.GraceNote( + grace_type="appoggiatura", + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=dict(type="quarter"), + ) + + part.add(note, n["onset_div"], n["onset_div"] + n["duration_div"]) + + if np.all(note_array["ts_beats"] == None): + warnings.warn("No time signatures found, assuming 4/4") + time_sigs = [(0, 4, 4)] + else: + time_sigs = [(0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"])] + for n in note_array: + if n["ts_beats"] != time_sigs[-1][1] and n["ts_beat_type"] != time_sigs[-1][2]: + time_sigs.append((n["onset_div"], n["ts_beats"], n["ts_beat_type"])) + time_sigs = np.array(note_array[["onset_div", "ts_beats", "ts_beat_type"]], dtype=int) + + # for convenience we add the end times for each time signature + ts_end_times = np.r_[time_sigs[1:, 0], np.iinfo(int).max] + time_sigs = np.column_stack((time_sigs, ts_end_times)) + + warnings.warn("add time sigs and measures", stacklevel=2) + + for ts_start, num, den, ts_end in time_sigs: + time_sig = score.TimeSignature(num.item(), den.item()) + part.add(time_sig, ts_start.item()) + + score.add_measures(part) + + warnings.warn("tie notes", stacklevel=2) + # tie notes where necessary (across measure boundaries, and within measures + # notes with compound duration) + score.tie_notes(part) + + warnings.warn("find tuplets", stacklevel=2) + # apply simplistic tuplet finding heuristic + score.find_tuplets(part) + + warnings.warn("done create_part", stacklevel=2) + return part + +def note_array_to_part(note_array : Union[np.ndarray, list], part_id="", assign_note_ids:bool=True, ensurelist:bool=False, estimate_key:bool=False)-> Union[Union[Part, PartGroup], list]: + """ + A generic function to transform an enriched note_array to part. + + The function can be used for many different occasions, i.e. load_score_midi, part_from_match, part_from_graph, etc. + This function requires a note array that contains time signatures and key signatures(optional - can also estimate it automatically). + Note array should contain the following fields: + - onset_div or onset_beat + - duration_div or duration_beat + - pitch + - ts_beats + - ts_beat_type + - key_mode(optional) + - key_fifths(optional) + - id(required but can also be empty) + + Parameters + ---------- + note_array : structure array or list of structured arrays + + assign_note_ids : bool (optional) + Assign note_ids + ensurelist: bool (optional) + ensure that output part is a list + estimate_key: bool (optional) + + Returns + ------- + part : list or Part or PartGroup + Maybe should return score + """ + if isinstance(note_array, list): + parts = [note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for i, x in enumerate(note_array)] + return parts + + if not isinstance(note_array, np.ndarray): + raise TypeError("The note array does not have the correct format.") + if len(note_array == 0): + raise ValueError("The note array is empty.") + + if assign_note_ids or np.all(note_array["id"] == note_array["id"][0]): + note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] + note_array["id"] = np.array(note_ids) + + dtypes = note_array.dtype.names + if not "ts_beats" in dtypes: + raise AttributeError("The note array does not contain a time signature.") + if all([x in dtypes for x in ["onset_div", "pitch", "duration_div", "onset_beat", "duration_beat"]]): + pass + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch", "global_score_time"]]): + x = np.unique(note_array["onset_beat"]) + renorm_onset_beat = False + if x.max() > note_array["ts_beats"].max(): + pass + else: + for unique_ons in x: + tmp = note_array[note_array["onset_beat"] == unique_ons] + if not np.all(tmp["global_score_time"] == tmp[0]["global_score_time"]): + renorm_onset_beat = True + break + + if renorm_onset_beat: + tmp = note_array[0]["onset_beat"] + tmp_ts = note_array[0]["ts_beats"] + for idx in range(1, len(note_array)): + if note_array[idx]["onset_beat"] < tmp: + tmp_ts = tmp_ts + note_array[idx]["ts_beats"] if note_array[idx]["onset_beat"] + tmp_ts < tmp else tmp_ts + tmp = note_array[idx]["onset_beat"] + tmp_ts + note_array[idx]["onset_beat"] = tmp + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch"]]): + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_div", "pitch", "duration_div"]]): + pass + else: + raise AttributeError("The note array does not include the necessary fields.") + + if "voice" in dtypes: + estimate_voice_info = False + part_voice_list = note_array["voice"] + else: + part_voice_list = np.empty(len(note_array)) + + if not all(x in dtypes for x in ['step', 'alter', 'octave']): + warnings.warn("pitch spelling") + spelling_global = analysis.estimate_spelling(note_array) + note_array = rfn.merge_arrays((note_array, spelling_global)) + + + if estimate_voice_info: + warnings.warn("voice estimation", stacklevel=2) + # TODO: deal with zero duration notes in note_array. + # Zero duration notes are currently deleted + estimated_voices = analysis.estimate_voices(note_array) + assert len(part_voice_list) == len(estimated_voices) + for part_voice, voice_est in zip(part_voice_list, estimated_voices): + if part_voice[1] is None: + part_voice[1] = voice_est + + if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): + warnings.warn("key estimation", stacklevel=2) + k_name = analysis.estimate_key(note_array) + global_key_sigs = [(0, k_name)] + + divs = int(note_array[0]["duration_div"] / note_array[0]["duration_beat"]) + part = create_part( + ticks= divs, + note_array=note_array, + key_sigs=sorted(global_key_sigs), + part_id=part_id, + part_name=None, + ) + return part + + if __name__ == "__main__": import doctest From 510d83674147d99a973218a39e09921562a85fc4 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 25 May 2022 19:15:04 -0400 Subject: [PATCH 002/122] note_array to part and helper functions. --- partitura/utils/music.py | 221 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index c78dc151..95dd2cfe 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -6,6 +6,20 @@ from scipy.interpolate import interp1d from scipy.sparse import csc_matrix +# imports for note_array 2 part +from partitura.score import Part, PartGroup +from typing import Union +import numpy.lib.recfunctions as rfn +from fractions import Fraction +import partitura.musicanalysis as analysis +import partitura.score as score +from partitura.utils import ( + estimate_symbolic_duration, + key_name_to_fifths_mode, + estimate_clef_properties, +) + + from partitura.utils.generic import find_nearest, search, iter_current_next MIDI_BASE_CLASS = {"c": 0, "d": 2, "e": 4, "f": 5, "g": 7, "a": 9, "b": 11} @@ -2189,6 +2203,213 @@ def get_matched_notes(spart_note_array, ppart_note_array, alignment): return np.array(matched_idxs) + +def create_divs_from_beats(note_array): + duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] + onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] + divs = np.lcm.reduce( + [Fraction(ix).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) + onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) + duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) + na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) + return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) + + + +def create_part( + ticks, + note_array, + key_sigs, + part_id=None, + part_name=None, +): + warnings.warn("create_part", stacklevel=2) + + part = score.Part(part_id, part_name=part_name) + part.set_quarter_duration(0, ticks) + + clef = score.Clef( + number=1, **estimate_clef_properties(note_array["pitch"]) + ) + part.add(clef, 0) + for t, name in key_sigs: + fifths, mode = key_name_to_fifths_mode(name) + part.add(score.KeySignature(fifths, mode), t) + + warnings.warn("add notes", stacklevel=2) + + for n in note_array: + if n["duration_div"] > 0: + note = score.Note( + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=estimate_symbolic_duration(n["duration_div"], ticks) + ) + else: + note = score.GraceNote( + grace_type="appoggiatura", + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=dict(type="quarter"), + ) + + part.add(note, n["onset_div"], n["onset_div"] + n["duration_div"]) + + if np.all(note_array["ts_beats"] == None): + warnings.warn("No time signatures found, assuming 4/4") + time_sigs = [(0, 4, 4)] + else: + time_sigs = [(0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"])] + for n in note_array: + if n["ts_beats"] != time_sigs[-1][1] and n["ts_beat_type"] != time_sigs[-1][2]: + time_sigs.append((n["onset_div"], n["ts_beats"], n["ts_beat_type"])) + time_sigs = np.array(note_array[["onset_div", "ts_beats", "ts_beat_type"]], dtype=int) + + # for convenience we add the end times for each time signature + ts_end_times = np.r_[time_sigs[1:, 0], np.iinfo(int).max] + time_sigs = np.column_stack((time_sigs, ts_end_times)) + + warnings.warn("add time sigs and measures", stacklevel=2) + + for ts_start, num, den, ts_end in time_sigs: + time_sig = score.TimeSignature(num.item(), den.item()) + part.add(time_sig, ts_start.item()) + + score.add_measures(part) + + warnings.warn("tie notes", stacklevel=2) + # tie notes where necessary (across measure boundaries, and within measures + # notes with compound duration) + score.tie_notes(part) + + warnings.warn("find tuplets", stacklevel=2) + # apply simplistic tuplet finding heuristic + score.find_tuplets(part) + + warnings.warn("done create_part", stacklevel=2) + return part + +def note_array_to_part(note_array : Union[np.ndarray, list], part_id="", assign_note_ids:bool=True, ensurelist:bool=False, estimate_key:bool=False)-> Union[Union[Part, PartGroup], list]: + """ + A generic function to transform an enriched note_array to part. + + The function can be used for many different occasions, i.e. load_score_midi, part_from_match, part_from_graph, etc. + This function requires a note array that contains time signatures and key signatures(optional - can also estimate it automatically). + Note array should contain the following fields: + - onset_div or onset_beat + - duration_div or duration_beat + - pitch + - ts_beats + - ts_beat_type + - key_mode(optional) + - key_fifths(optional) + - id(required but can also be empty) + + Parameters + ---------- + note_array : structure array or list of structured arrays + + assign_note_ids : bool (optional) + Assign note_ids + ensurelist: bool (optional) + ensure that output part is a list + estimate_key: bool (optional) + + Returns + ------- + part : list or Part or PartGroup + Maybe should return score + """ + if isinstance(note_array, list): + parts = [note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for i, x in enumerate(note_array)] + return parts + + if not isinstance(note_array, np.ndarray): + raise TypeError("The note array does not have the correct format.") + if len(note_array == 0): + raise ValueError("The note array is empty.") + + if assign_note_ids or np.all(note_array["id"] == note_array["id"][0]): + note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] + note_array["id"] = np.array(note_ids) + + dtypes = note_array.dtype.names + if not "ts_beats" in dtypes: + raise AttributeError("The note array does not contain a time signature.") + if all([x in dtypes for x in ["onset_div", "pitch", "duration_div", "onset_beat", "duration_beat"]]): + pass + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch", "global_score_time"]]): + x = np.unique(note_array["onset_beat"]) + renorm_onset_beat = False + if x.max() > note_array["ts_beats"].max(): + pass + else: + for unique_ons in x: + tmp = note_array[note_array["onset_beat"] == unique_ons] + if not np.all(tmp["global_score_time"] == tmp[0]["global_score_time"]): + renorm_onset_beat = True + break + + if renorm_onset_beat: + tmp = note_array[0]["onset_beat"] + tmp_ts = note_array[0]["ts_beats"] + for idx in range(1, len(note_array)): + if note_array[idx]["onset_beat"] < tmp: + tmp_ts = tmp_ts + note_array[idx]["ts_beats"] if note_array[idx]["onset_beat"] + tmp_ts < tmp else tmp_ts + tmp = note_array[idx]["onset_beat"] + tmp_ts + note_array[idx]["onset_beat"] = tmp + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch"]]): + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_div", "pitch", "duration_div"]]): + pass + else: + raise AttributeError("The note array does not include the necessary fields.") + + if "voice" in dtypes: + estimate_voice_info = False + part_voice_list = note_array["voice"] + else: + part_voice_list = np.empty(len(note_array)) + + if not all(x in dtypes for x in ['step', 'alter', 'octave']): + warnings.warn("pitch spelling") + spelling_global = analysis.estimate_spelling(note_array) + note_array = rfn.merge_arrays((note_array, spelling_global)) + + + if estimate_voice_info: + warnings.warn("voice estimation", stacklevel=2) + # TODO: deal with zero duration notes in note_array. + # Zero duration notes are currently deleted + estimated_voices = analysis.estimate_voices(note_array) + assert len(part_voice_list) == len(estimated_voices) + for part_voice, voice_est in zip(part_voice_list, estimated_voices): + if part_voice[1] is None: + part_voice[1] = voice_est + + if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): + warnings.warn("key estimation", stacklevel=2) + k_name = analysis.estimate_key(note_array) + global_key_sigs = [(0, k_name)] + + divs = int(note_array[0]["duration_div"] / note_array[0]["duration_beat"]) + part = create_part( + ticks= divs, + note_array=note_array, + key_sigs=sorted(global_key_sigs), + part_id=part_id, + part_name=None, + ) + return part + + if __name__ == "__main__": import doctest From 800c6b19e6f70fa5e59be59e83edf032799201e9 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 8 Jun 2022 15:09:39 +0200 Subject: [PATCH 003/122] note_array_to_part function. --- partitura/utils/music.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 95dd2cfe..4e7e3317 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -1,6 +1,7 @@ #!/usr/bin/env python from collections import defaultdict import re +from partitura.io.importmidi import create_part import warnings import numpy as np from scipy.interpolate import interp1d @@ -2203,7 +2204,6 @@ def get_matched_notes(spart_note_array, ppart_note_array, alignment): return np.array(matched_idxs) - def create_divs_from_beats(note_array): duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] @@ -2215,7 +2215,6 @@ def create_divs_from_beats(note_array): return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) - def create_part( ticks, note_array, @@ -2295,6 +2294,7 @@ def create_part( warnings.warn("done create_part", stacklevel=2) return part + def note_array_to_part(note_array : Union[np.ndarray, list], part_id="", assign_note_ids:bool=True, ensurelist:bool=False, estimate_key:bool=False)-> Union[Union[Part, PartGroup], list]: """ A generic function to transform an enriched note_array to part. From f4bc79192301c8793aecda01fb120694b2795aa7 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 25 May 2022 19:15:04 -0400 Subject: [PATCH 004/122] note_array to part and helper functions. --- partitura/utils/music.py | 221 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 7747da94..f0a871da 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -6,6 +6,20 @@ from scipy.interpolate import interp1d from scipy.sparse import csc_matrix +# imports for note_array 2 part +from partitura.score import Part, PartGroup +from typing import Union +import numpy.lib.recfunctions as rfn +from fractions import Fraction +import partitura.musicanalysis as analysis +import partitura.score as score +from partitura.utils import ( + estimate_symbolic_duration, + key_name_to_fifths_mode, + estimate_clef_properties, +) + + from partitura.utils.generic import find_nearest, search, iter_current_next MIDI_BASE_CLASS = {"c": 0, "d": 2, "e": 4, "f": 5, "g": 7, "a": 9, "b": 11} @@ -2645,6 +2659,213 @@ def get_matched_notes(spart_note_array, ppart_note_array, alignment): return np.array(matched_idxs) + +def create_divs_from_beats(note_array): + duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] + onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] + divs = np.lcm.reduce( + [Fraction(ix).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) + onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) + duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) + na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) + return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) + + + +def create_part( + ticks, + note_array, + key_sigs, + part_id=None, + part_name=None, +): + warnings.warn("create_part", stacklevel=2) + + part = score.Part(part_id, part_name=part_name) + part.set_quarter_duration(0, ticks) + + clef = score.Clef( + number=1, **estimate_clef_properties(note_array["pitch"]) + ) + part.add(clef, 0) + for t, name in key_sigs: + fifths, mode = key_name_to_fifths_mode(name) + part.add(score.KeySignature(fifths, mode), t) + + warnings.warn("add notes", stacklevel=2) + + for n in note_array: + if n["duration_div"] > 0: + note = score.Note( + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=estimate_symbolic_duration(n["duration_div"], ticks) + ) + else: + note = score.GraceNote( + grace_type="appoggiatura", + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=dict(type="quarter"), + ) + + part.add(note, n["onset_div"], n["onset_div"] + n["duration_div"]) + + if np.all(note_array["ts_beats"] == None): + warnings.warn("No time signatures found, assuming 4/4") + time_sigs = [(0, 4, 4)] + else: + time_sigs = [(0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"])] + for n in note_array: + if n["ts_beats"] != time_sigs[-1][1] and n["ts_beat_type"] != time_sigs[-1][2]: + time_sigs.append((n["onset_div"], n["ts_beats"], n["ts_beat_type"])) + time_sigs = np.array(note_array[["onset_div", "ts_beats", "ts_beat_type"]], dtype=int) + + # for convenience we add the end times for each time signature + ts_end_times = np.r_[time_sigs[1:, 0], np.iinfo(int).max] + time_sigs = np.column_stack((time_sigs, ts_end_times)) + + warnings.warn("add time sigs and measures", stacklevel=2) + + for ts_start, num, den, ts_end in time_sigs: + time_sig = score.TimeSignature(num.item(), den.item()) + part.add(time_sig, ts_start.item()) + + score.add_measures(part) + + warnings.warn("tie notes", stacklevel=2) + # tie notes where necessary (across measure boundaries, and within measures + # notes with compound duration) + score.tie_notes(part) + + warnings.warn("find tuplets", stacklevel=2) + # apply simplistic tuplet finding heuristic + score.find_tuplets(part) + + warnings.warn("done create_part", stacklevel=2) + return part + +def note_array_to_part(note_array : Union[np.ndarray, list], part_id="", assign_note_ids:bool=True, ensurelist:bool=False, estimate_key:bool=False)-> Union[Union[Part, PartGroup], list]: + """ + A generic function to transform an enriched note_array to part. + + The function can be used for many different occasions, i.e. load_score_midi, part_from_match, part_from_graph, etc. + This function requires a note array that contains time signatures and key signatures(optional - can also estimate it automatically). + Note array should contain the following fields: + - onset_div or onset_beat + - duration_div or duration_beat + - pitch + - ts_beats + - ts_beat_type + - key_mode(optional) + - key_fifths(optional) + - id(required but can also be empty) + + Parameters + ---------- + note_array : structure array or list of structured arrays + + assign_note_ids : bool (optional) + Assign note_ids + ensurelist: bool (optional) + ensure that output part is a list + estimate_key: bool (optional) + + Returns + ------- + part : list or Part or PartGroup + Maybe should return score + """ + if isinstance(note_array, list): + parts = [note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for i, x in enumerate(note_array)] + return parts + + if not isinstance(note_array, np.ndarray): + raise TypeError("The note array does not have the correct format.") + if len(note_array == 0): + raise ValueError("The note array is empty.") + + if assign_note_ids or np.all(note_array["id"] == note_array["id"][0]): + note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] + note_array["id"] = np.array(note_ids) + + dtypes = note_array.dtype.names + if not "ts_beats" in dtypes: + raise AttributeError("The note array does not contain a time signature.") + if all([x in dtypes for x in ["onset_div", "pitch", "duration_div", "onset_beat", "duration_beat"]]): + pass + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch", "global_score_time"]]): + x = np.unique(note_array["onset_beat"]) + renorm_onset_beat = False + if x.max() > note_array["ts_beats"].max(): + pass + else: + for unique_ons in x: + tmp = note_array[note_array["onset_beat"] == unique_ons] + if not np.all(tmp["global_score_time"] == tmp[0]["global_score_time"]): + renorm_onset_beat = True + break + + if renorm_onset_beat: + tmp = note_array[0]["onset_beat"] + tmp_ts = note_array[0]["ts_beats"] + for idx in range(1, len(note_array)): + if note_array[idx]["onset_beat"] < tmp: + tmp_ts = tmp_ts + note_array[idx]["ts_beats"] if note_array[idx]["onset_beat"] + tmp_ts < tmp else tmp_ts + tmp = note_array[idx]["onset_beat"] + tmp_ts + note_array[idx]["onset_beat"] = tmp + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch"]]): + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_div", "pitch", "duration_div"]]): + pass + else: + raise AttributeError("The note array does not include the necessary fields.") + + if "voice" in dtypes: + estimate_voice_info = False + part_voice_list = note_array["voice"] + else: + part_voice_list = np.empty(len(note_array)) + + if not all(x in dtypes for x in ['step', 'alter', 'octave']): + warnings.warn("pitch spelling") + spelling_global = analysis.estimate_spelling(note_array) + note_array = rfn.merge_arrays((note_array, spelling_global)) + + + if estimate_voice_info: + warnings.warn("voice estimation", stacklevel=2) + # TODO: deal with zero duration notes in note_array. + # Zero duration notes are currently deleted + estimated_voices = analysis.estimate_voices(note_array) + assert len(part_voice_list) == len(estimated_voices) + for part_voice, voice_est in zip(part_voice_list, estimated_voices): + if part_voice[1] is None: + part_voice[1] = voice_est + + if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): + warnings.warn("key estimation", stacklevel=2) + k_name = analysis.estimate_key(note_array) + global_key_sigs = [(0, k_name)] + + divs = int(note_array[0]["duration_div"] / note_array[0]["duration_beat"]) + part = create_part( + ticks= divs, + note_array=note_array, + key_sigs=sorted(global_key_sigs), + part_id=part_id, + part_name=None, + ) + return part + + if __name__ == "__main__": import doctest From 61fe129e68f46a90065cac10d038d2a02d3eeff0 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 8 Jun 2022 15:10:41 +0200 Subject: [PATCH 005/122] styling corrections. --- partitura/utils/music.py | 1 - 1 file changed, 1 deletion(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index f0a871da..7f002069 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -2219,7 +2219,6 @@ def note_array_from_note_list( return note_array - def rest_array_from_rest_list( rest_list, beat_map=None, From 358f7a8f65e603368ff4ee60c6e46d540bf57c5d Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 8 Jun 2022 15:09:39 +0200 Subject: [PATCH 006/122] note_array_to_part function. --- partitura/utils/music.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 7f002069..038c65c1 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -1,6 +1,7 @@ #!/usr/bin/env python from collections import defaultdict import re +from partitura.io.importmidi import create_part import warnings import numpy as np from scipy.interpolate import interp1d @@ -2658,7 +2659,6 @@ def get_matched_notes(spart_note_array, ppart_note_array, alignment): return np.array(matched_idxs) - def create_divs_from_beats(note_array): duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] @@ -2670,7 +2670,6 @@ def create_divs_from_beats(note_array): return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) - def create_part( ticks, note_array, @@ -2750,6 +2749,7 @@ def create_part( warnings.warn("done create_part", stacklevel=2) return part + def note_array_to_part(note_array : Union[np.ndarray, list], part_id="", assign_note_ids:bool=True, ensurelist:bool=False, estimate_key:bool=False)-> Union[Union[Part, PartGroup], list]: """ A generic function to transform an enriched note_array to part. From c9a503cce8c3d3f9ac92ed74b0df79347efca1fb Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 8 Jun 2022 16:32:15 +0200 Subject: [PATCH 007/122] Note array to part test and corrections. --- partitura/musicanalysis/__init__.py | 2 +- partitura/musicanalysis/note_array_to_part.py | 231 ++++++++++++++++++ partitura/utils/music.py | 222 ----------------- tests/test_note_array.py | 12 + 4 files changed, 244 insertions(+), 223 deletions(-) create mode 100644 partitura/musicanalysis/note_array_to_part.py diff --git a/partitura/musicanalysis/__init__.py b/partitura/musicanalysis/__init__.py index b37dc0fe..a7c9d44b 100644 --- a/partitura/musicanalysis/__init__.py +++ b/partitura/musicanalysis/__init__.py @@ -11,7 +11,7 @@ from .tonal_tension import estimate_tonaltension from .note_features import make_note_feats, compute_note_array, full_note_array, make_rest_feats, make_rest_features from .performance_codec import encode_performance, decode_performance - +from .note_array_to_part import note_array_to_part __all__ = [ "estimate_voices", diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py new file mode 100644 index 00000000..18b771c6 --- /dev/null +++ b/partitura/musicanalysis/note_array_to_part.py @@ -0,0 +1,231 @@ +from partitura.score import Part, PartGroup +from partitura.utils import (estimate_symbolic_duration, estimate_clef_properties, key_name_to_fifths_mode, fifths_mode_to_key_name) +import warnings +import numpy as np +from typing import Union +import numpy.lib.recfunctions as rfn +from fractions import Fraction +import partitura.musicanalysis as analysis +import partitura.score as score + + +def create_divs_from_beats(note_array): + duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] + onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] + divs = np.lcm.reduce( + [Fraction(ix).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) + onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) + duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) + na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) + return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) + + +def create_part( + ticks, + note_array, + key_sigs, + part_id=None, + part_name=None, +): + warnings.warn("create_part", stacklevel=2) + + part = score.Part(part_id, part_name=part_name) + part.set_quarter_duration(0, ticks) + + clef = score.Clef( + number=1, **estimate_clef_properties(note_array["pitch"]) + ) + part.add(clef, 0) + for t_start, name, t_end in key_sigs: + fifths, mode = key_name_to_fifths_mode(name) + t_start, t_end = int(t_start), int(t_end) + part.add(score.KeySignature(fifths, mode), t_start, t_end) + + warnings.warn("add notes", stacklevel=2) + + for n in note_array: + if n["duration_div"] > 0: + note = score.Note( + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=estimate_symbolic_duration(n["duration_div"], ticks) + ) + else: + note = score.GraceNote( + grace_type="appoggiatura", + step=n["step"], + octave=n["octave"], + alter=n["alter"], + voice=int(n["voice"] or 0), + id=n["id"], + symbolic_duration=dict(type="quarter"), + ) + + part.add(note, n["onset_div"], n["onset_div"] + n["duration_div"]) + + if np.all(note_array["ts_beats"] == None): + warnings.warn("No time signatures found, assuming 4/4") + time_sigs = [[0, 4, 4]] + else: + time_sigs = [[0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"]]] + for n in note_array: + if n["ts_beats"] != time_sigs[-1][1] or n["ts_beat_type"] != time_sigs[-1][2]: + time_sigs.append([n["onset_div"], n["ts_beats"], n["ts_beat_type"]]) + time_sigs = np.array(time_sigs) + + # for convenience we add the end times for each time signature + ts_end_times = np.r_[time_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] + time_sigs = np.column_stack((time_sigs, ts_end_times)) + + warnings.warn("add time sigs and measures", stacklevel=2) + + for ts_start, num, den, ts_end in time_sigs: + time_sig = score.TimeSignature(num.item(), den.item()) + part.add(time_sig, ts_start, ts_end) + + score.add_measures(part) + + warnings.warn("tie notes", stacklevel=2) + # tie notes where necessary (across measure boundaries, and within measures + # notes with compound duration) + score.tie_notes(part) + + warnings.warn("find tuplets", stacklevel=2) + # apply simplistic tuplet finding heuristic + score.find_tuplets(part) + + warnings.warn("done create_part", stacklevel=2) + return part + + +def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", assign_note_ids: bool = True, + ensurelist: bool = False, estimate_key: bool = False) -> Union[Union[Part, PartGroup], list]: + """ + A generic function to transform an enriched note_array to part. + + The function can be used for many different occasions, i.e. load_score_midi, part_from_match, part_from_graph, etc. + This function requires a note array that contains time signatures and key signatures(optional - can also estimate it automatically). + Note array should contain the following fields: + - onset_div or onset_beat + - duration_div or duration_beat + - pitch + - ts_beats + - ts_beat_type + - key_mode(optional) + - key_fifths(optional) + - id(required but can also be empty) + + Parameters + ---------- + note_array : structure array or list of structured arrays + + assign_note_ids : bool (optional) + Assign note_ids + ensurelist: bool (optional) + ensure that output part is a list + estimate_key: bool (optional) + + Returns + ------- + part : list or Part or PartGroup + Maybe should return score + """ + + + if isinstance(note_array, list): + parts = [ + note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for + i, x in enumerate(note_array)] + return parts + + if not isinstance(note_array, np.ndarray): + raise TypeError("The note array does not have the correct format.") + if len(note_array) == 0: + raise ValueError("The note array is empty.") + + if assign_note_ids or np.all(note_array["id"] == note_array["id"][0]): + note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] + note_array["id"] = np.array(note_ids) + + dtypes = note_array.dtype.names + if not "ts_beats" in dtypes: + raise AttributeError("The note array does not contain a time signature.") + if all([x in dtypes for x in ["onset_div", "pitch", "duration_div", "onset_beat", "duration_beat"]]): + pass + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch", "global_score_time"]]): + x = np.unique(note_array["onset_beat"]) + renorm_onset_beat = False + if x.max() > note_array["ts_beats"].max(): + pass + else: + for unique_ons in x: + tmp = note_array[note_array["onset_beat"] == unique_ons] + if not np.all(tmp["global_score_time"] == tmp[0]["global_score_time"]): + renorm_onset_beat = True + break + + if renorm_onset_beat: + tmp = note_array[0]["onset_beat"] + tmp_ts = note_array[0]["ts_beats"] + for idx in range(1, len(note_array)): + if note_array[idx]["onset_beat"] < tmp: + tmp_ts = tmp_ts + note_array[idx]["ts_beats"] if note_array[idx][ + "onset_beat"] + tmp_ts < tmp else tmp_ts + tmp = note_array[idx]["onset_beat"] + tmp_ts + note_array[idx]["onset_beat"] = tmp + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch"]]): + note_array = create_divs_from_beats(note_array) + elif all([x in dtypes for x in ["onset_div", "pitch", "duration_div"]]): + pass + else: + raise AttributeError("The note array does not include the necessary fields.") + + if "voice" in dtypes: + estimate_voice_info = False + part_voice_list = note_array["voice"] + else: + part_voice_list = np.empty(len(note_array)) + + if not all(x in dtypes for x in ['step', 'alter', 'octave']): + warnings.warn("pitch spelling") + spelling_global = analysis.estimate_spelling(note_array) + note_array = rfn.merge_arrays((note_array, spelling_global)) + + if estimate_voice_info: + warnings.warn("voice estimation", stacklevel=2) + # TODO: deal with zero duration notes in note_array. + # Zero duration notes are currently deleted + estimated_voices = analysis.estimate_voices(note_array) + assert len(part_voice_list) == len(estimated_voices) + for part_voice, voice_est in zip(part_voice_list, estimated_voices): + if part_voice[1] is None: + part_voice[1] = voice_est + + if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): + warnings.warn("key estimation", stacklevel=2) + k_name = analysis.estimate_key(note_array) + global_key_sigs = [[0, k_name]] + else: + global_key_sigs = [[0, fifths_mode_to_key_name(note_array[0]["ks_fifths"], note_array[0]["ks_mode"])]] + for n in note_array: + if n["ts_beats"] != global_key_sigs[-1][1] or n["ts_beat_type"] != global_key_sigs[-1][2]: + global_key_sigs.append([n["onset_div"], fifths_mode_to_key_name(n["ks_fifths"], n["ks_mode"])]) + global_key_sigs = np.array(global_key_sigs) + # for convenience we add the end times for each time signature + ks_end_times = np.r_[global_key_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] + global_key_sigs = np.column_stack((global_key_sigs, ks_end_times)) + + + divs = int((note_array[0]["duration_div"] / note_array[0]["duration_beat"])*(note_array[0]["ts_beat_type"]/4)) + part = create_part( + ticks=divs, + note_array=note_array, + key_sigs=global_key_sigs, + part_id=part_id, + part_name=None, + ) + return part \ No newline at end of file diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 038c65c1..ab04cf86 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -1,26 +1,10 @@ #!/usr/bin/env python from collections import defaultdict import re -from partitura.io.importmidi import create_part import warnings import numpy as np from scipy.interpolate import interp1d from scipy.sparse import csc_matrix - -# imports for note_array 2 part -from partitura.score import Part, PartGroup -from typing import Union -import numpy.lib.recfunctions as rfn -from fractions import Fraction -import partitura.musicanalysis as analysis -import partitura.score as score -from partitura.utils import ( - estimate_symbolic_duration, - key_name_to_fifths_mode, - estimate_clef_properties, -) - - from partitura.utils.generic import find_nearest, search, iter_current_next MIDI_BASE_CLASS = {"c": 0, "d": 2, "e": 4, "f": 5, "g": 7, "a": 9, "b": 11} @@ -2659,212 +2643,6 @@ def get_matched_notes(spart_note_array, ppart_note_array, alignment): return np.array(matched_idxs) -def create_divs_from_beats(note_array): - duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] - onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] - divs = np.lcm.reduce( - [Fraction(ix).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) - onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) - duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) - na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) - return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) - - -def create_part( - ticks, - note_array, - key_sigs, - part_id=None, - part_name=None, -): - warnings.warn("create_part", stacklevel=2) - - part = score.Part(part_id, part_name=part_name) - part.set_quarter_duration(0, ticks) - - clef = score.Clef( - number=1, **estimate_clef_properties(note_array["pitch"]) - ) - part.add(clef, 0) - for t, name in key_sigs: - fifths, mode = key_name_to_fifths_mode(name) - part.add(score.KeySignature(fifths, mode), t) - - warnings.warn("add notes", stacklevel=2) - - for n in note_array: - if n["duration_div"] > 0: - note = score.Note( - step=n["step"], - octave=n["octave"], - alter=n["alter"], - voice=int(n["voice"] or 0), - id=n["id"], - symbolic_duration=estimate_symbolic_duration(n["duration_div"], ticks) - ) - else: - note = score.GraceNote( - grace_type="appoggiatura", - step=n["step"], - octave=n["octave"], - alter=n["alter"], - voice=int(n["voice"] or 0), - id=n["id"], - symbolic_duration=dict(type="quarter"), - ) - - part.add(note, n["onset_div"], n["onset_div"] + n["duration_div"]) - - if np.all(note_array["ts_beats"] == None): - warnings.warn("No time signatures found, assuming 4/4") - time_sigs = [(0, 4, 4)] - else: - time_sigs = [(0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"])] - for n in note_array: - if n["ts_beats"] != time_sigs[-1][1] and n["ts_beat_type"] != time_sigs[-1][2]: - time_sigs.append((n["onset_div"], n["ts_beats"], n["ts_beat_type"])) - time_sigs = np.array(note_array[["onset_div", "ts_beats", "ts_beat_type"]], dtype=int) - - # for convenience we add the end times for each time signature - ts_end_times = np.r_[time_sigs[1:, 0], np.iinfo(int).max] - time_sigs = np.column_stack((time_sigs, ts_end_times)) - - warnings.warn("add time sigs and measures", stacklevel=2) - - for ts_start, num, den, ts_end in time_sigs: - time_sig = score.TimeSignature(num.item(), den.item()) - part.add(time_sig, ts_start.item()) - - score.add_measures(part) - - warnings.warn("tie notes", stacklevel=2) - # tie notes where necessary (across measure boundaries, and within measures - # notes with compound duration) - score.tie_notes(part) - - warnings.warn("find tuplets", stacklevel=2) - # apply simplistic tuplet finding heuristic - score.find_tuplets(part) - - warnings.warn("done create_part", stacklevel=2) - return part - - -def note_array_to_part(note_array : Union[np.ndarray, list], part_id="", assign_note_ids:bool=True, ensurelist:bool=False, estimate_key:bool=False)-> Union[Union[Part, PartGroup], list]: - """ - A generic function to transform an enriched note_array to part. - - The function can be used for many different occasions, i.e. load_score_midi, part_from_match, part_from_graph, etc. - This function requires a note array that contains time signatures and key signatures(optional - can also estimate it automatically). - Note array should contain the following fields: - - onset_div or onset_beat - - duration_div or duration_beat - - pitch - - ts_beats - - ts_beat_type - - key_mode(optional) - - key_fifths(optional) - - id(required but can also be empty) - - Parameters - ---------- - note_array : structure array or list of structured arrays - - assign_note_ids : bool (optional) - Assign note_ids - ensurelist: bool (optional) - ensure that output part is a list - estimate_key: bool (optional) - - Returns - ------- - part : list or Part or PartGroup - Maybe should return score - """ - if isinstance(note_array, list): - parts = [note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for i, x in enumerate(note_array)] - return parts - - if not isinstance(note_array, np.ndarray): - raise TypeError("The note array does not have the correct format.") - if len(note_array == 0): - raise ValueError("The note array is empty.") - - if assign_note_ids or np.all(note_array["id"] == note_array["id"][0]): - note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] - note_array["id"] = np.array(note_ids) - - dtypes = note_array.dtype.names - if not "ts_beats" in dtypes: - raise AttributeError("The note array does not contain a time signature.") - if all([x in dtypes for x in ["onset_div", "pitch", "duration_div", "onset_beat", "duration_beat"]]): - pass - elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch", "global_score_time"]]): - x = np.unique(note_array["onset_beat"]) - renorm_onset_beat = False - if x.max() > note_array["ts_beats"].max(): - pass - else: - for unique_ons in x: - tmp = note_array[note_array["onset_beat"] == unique_ons] - if not np.all(tmp["global_score_time"] == tmp[0]["global_score_time"]): - renorm_onset_beat = True - break - - if renorm_onset_beat: - tmp = note_array[0]["onset_beat"] - tmp_ts = note_array[0]["ts_beats"] - for idx in range(1, len(note_array)): - if note_array[idx]["onset_beat"] < tmp: - tmp_ts = tmp_ts + note_array[idx]["ts_beats"] if note_array[idx]["onset_beat"] + tmp_ts < tmp else tmp_ts - tmp = note_array[idx]["onset_beat"] + tmp_ts - note_array[idx]["onset_beat"] = tmp - note_array = create_divs_from_beats(note_array) - elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch"]]): - note_array = create_divs_from_beats(note_array) - elif all([x in dtypes for x in ["onset_div", "pitch", "duration_div"]]): - pass - else: - raise AttributeError("The note array does not include the necessary fields.") - - if "voice" in dtypes: - estimate_voice_info = False - part_voice_list = note_array["voice"] - else: - part_voice_list = np.empty(len(note_array)) - - if not all(x in dtypes for x in ['step', 'alter', 'octave']): - warnings.warn("pitch spelling") - spelling_global = analysis.estimate_spelling(note_array) - note_array = rfn.merge_arrays((note_array, spelling_global)) - - - if estimate_voice_info: - warnings.warn("voice estimation", stacklevel=2) - # TODO: deal with zero duration notes in note_array. - # Zero duration notes are currently deleted - estimated_voices = analysis.estimate_voices(note_array) - assert len(part_voice_list) == len(estimated_voices) - for part_voice, voice_est in zip(part_voice_list, estimated_voices): - if part_voice[1] is None: - part_voice[1] = voice_est - - if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): - warnings.warn("key estimation", stacklevel=2) - k_name = analysis.estimate_key(note_array) - global_key_sigs = [(0, k_name)] - - divs = int(note_array[0]["duration_div"] / note_array[0]["duration_beat"]) - part = create_part( - ticks= divs, - note_array=note_array, - key_sigs=sorted(global_key_sigs), - part_id=part_id, - part_name=None, - ) - return part - - if __name__ == "__main__": import doctest diff --git a/tests/test_note_array.py b/tests/test_note_array.py index 2a8ec1c0..7828a1af 100644 --- a/tests/test_note_array.py +++ b/tests/test_note_array.py @@ -9,6 +9,7 @@ import partitura.score as score from partitura import load_musicxml from partitura.utils.music import note_array_from_part +from partitura.musicanalysis import note_array_to_part import numpy as np from tests import NOTE_ARRAY_TESTFILES @@ -79,6 +80,17 @@ def test_notearray_ts_beats(self): expected_musical_beats = [2, 2, 3, 3, 3, 4, 4, 4, 4, 2, 2, 2, 2] self.assertTrue(np.array_equal(note_array["ts_beats"], expected_musical_beats)) + def test_note_array_to_part(self): + part_orig = load_musicxml(NOTE_ARRAY_TESTFILES[0]) + note_array = part_orig.note_array(include_time_signature=True, include_metrical_position=True, include_grace_notes=True, include_pitch_spelling=True) + part_gen = note_array_to_part(note_array) + gen_array = part_gen.note_array() + self.assertTrue(np.all(gen_array["onset_beat"] == note_array["onset_beat"])) + self.assertTrue(np.all(gen_array["duration_beat"] == note_array["duration_beat"])) + self.assertTrue(np.all(gen_array["onset_div"] == note_array["onset_div"])) + self.assertTrue(np.all(gen_array["duration_div"] == note_array["duration_div"])) + self.assertTrue(np.all(gen_array["pitch"] == note_array["pitch"])) + if __name__ == "__main__": unittest.main() From 2d10bb381a26cd48eb7176b94a1a24f023679e65 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 29 Aug 2022 14:00:59 +0200 Subject: [PATCH 008/122] import score midi adaptation with note_array to part. --- partitura/io/importmidi.py | 223 ++++++++++-------- partitura/musicanalysis/note_array_to_part.py | 21 +- 2 files changed, 134 insertions(+), 110 deletions(-) diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index 6690e278..eb9f9d40 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -2,7 +2,7 @@ import numpy as np from collections import defaultdict import warnings - +from numpy.lib.recfunctions import unstructured_to_structured, merge_arrays import mido import partitura.score as score @@ -14,6 +14,7 @@ estimate_clef_properties, ) import partitura.musicanalysis as analysis +from partitura.musicanalysis import note_array_to_part __all__ = ["load_score_midi", "load_performance_midi"] @@ -407,112 +408,134 @@ def load_score_midi( # note_list = sorted(note for notes in # (notes_by_track_ch[key] for key in tr_ch_keys) for note in notes) note_list = [ - note + ("None",) + note for notes in (notes_by_track_ch[key] for key in tr_ch_keys) for note in notes ] note_array = np.array( note_list, - dtype=[("onset_div", int), ("pitch", int), ("duration_div", int)], + dtype=[("id", str), ("onset_div", int), ("pitch", int), ("duration_div", int)], ) - - warnings.warn("pitch spelling") - spelling_global = analysis.estimate_spelling(note_array) - - if estimate_voice_info: - warnings.warn("voice estimation", stacklevel=2) - # TODO: deal with zero duration notes in note_array. - # Zero duration notes are currently deleted - estimated_voices = analysis.estimate_voices(note_array) - assert len(part_voice_list) == len(estimated_voices) - for part_voice, voice_est in zip(part_voice_list, estimated_voices): - if part_voice[1] is None: - part_voice[1] = voice_est - - if estimate_key: - warnings.warn("key estimation", stacklevel=2) - _, mode, fifths = analysis.estimate_key(note_array) - key_sigs_by_track = {} - global_key_sigs = [(0, fifths_mode_to_key_name(fifths, mode))] - - if assign_note_ids: - note_ids = ["n{}".format(i) for i in range(len(note_array))] + if len(global_time_sigs) > 1: + ts_ends = np.r_[global_time_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] + ts_beats = np.zeros((len(note_array), 1)) + ts_beat_type = np.zeros((len(note_array), 1)) + for i, ts_start, ts_num, ts_dem, ts_end in enumerate(global_time_sigs): + ts_end = ts_ends[i] + mask = np.where(np.logical_and(note_array["onset_div"] >= ts_start, note_array["onset_div"] < ts_end)) + ts_beats[mask, 0] = ts_num + ts_beat_type[mask, 0] = ts_dem + elif len(global_time_sigs) == 0: + ts_beats = np.full((len(note_array), 1), 4) + ts_beat_type = np.full((len(note_array), 1), 4) else: - note_ids = [None for i in range(len(note_array))] - - time_sigs_by_part = defaultdict(set) - for tr, ts_list in time_sigs_by_track.items(): - for ts in ts_list: - for part in track_to_part_mapping[tr]: - time_sigs_by_part[part].add(ts) - for ts in global_time_sigs: - for part in set(part for _, part, _ in group_part_voice_keys): - time_sigs_by_part[part].add(ts) - - key_sigs_by_part = defaultdict(set) - for tr, ks_list in key_sigs_by_track.items(): - for ks in ks_list: - for part in track_to_part_mapping[tr]: - key_sigs_by_part[part].add(ks) - for ks in global_key_sigs: - for part in set(part for _, part, _ in group_part_voice_keys): - key_sigs_by_part[part].add(ks) - - # names_by_part = defaultdict(set) - # for tr_ch, pg_p_v in zip(tr_ch_keys, group_part_voice_keys): - # print(tr_ch, pg_p_v) - # for tr, name in track_names_by_track.items(): - # print(tr, track_to_part_mapping, name) - # for part in track_to_part_mapping[tr]: - # names_by_part[part] = name - - notes_by_part = defaultdict(list) - for (part, voice), note, spelling, note_id in zip( - part_voice_list, note_list, spelling_global, note_ids - ): - notes_by_part[part].append((note, voice, spelling, note_id)) - - partlist = [] - part_to_part_group = dict((p, pg) for pg, p, _ in group_part_voice_keys) - part_groups = {} - for part_nr, note_info in notes_by_part.items(): - notes, voices, spellings, note_ids = zip(*note_info) - part = create_part( - divs, - notes, - spellings, - voices, - note_ids, - sorted(time_sigs_by_part[part_nr]), - sorted(key_sigs_by_part[part_nr]), - part_id="P{}".format(part_nr + 1), - part_name=part_names.get(part_nr, None), - ) - - # print(part.pretty()) - # if this part has an associated part_group number we create a PartGroup - # if necessary, and add the part to that. The newly created PartGroup is - # then added to the partlist. - pg_nr = part_to_part_group[part_nr] - if pg_nr is None: - partlist.append(part) - else: - if pg_nr not in part_groups: - part_groups[pg_nr] = score.PartGroup( - group_name=group_names.get(pg_nr, None) - ) - partlist.append(part_groups[pg_nr]) - part_groups[pg_nr].children.append(part) - - # add tempos to first part - part = next(score.iter_parts(partlist)) - for t, qpm in global_tempos: - part.add(score.Tempo(qpm, unit="q"), t) - - if not ensure_list and len(partlist) == 1: - return partlist[0] - else: - return partlist + ts_beats = np.full((len(note_array), 1), global_time_sigs[0][1]) + ts_beat_type = np.full((len(note_array), 1), global_time_sigs[0][2]) + + t_sigs = np.hstack((ts_beats, ts_beat_type)) + t_sigs = unstructured_to_structured(t_sigs, dtype=np.dtype([("ts_beats", int), ("ts_beat_type", int)])) + # TODO: deal with zero duration notes in note_array. + note_array = merge_arrays((note_array, t_sigs), flatten=True) + part = note_array_to_part(note_array, divs=divs, assign_note_ids=True) + return part + # + # warnings.warn("pitch spelling") + # spelling_global = analysis.estimate_spelling(note_array) + # + # if estimate_voice_info: + # warnings.warn("voice estimation", stacklevel=2) + # # TODO: deal with zero duration notes in note_array. + # # Zero duration notes are currently deleted + # estimated_voices = analysis.estimate_voices(note_array) + # assert len(part_voice_list) == len(estimated_voices) + # for part_voice, voice_est in zip(part_voice_list, estimated_voices): + # if part_voice[1] is None: + # part_voice[1] = voice_est + # + # if estimate_key: + # warnings.warn("key estimation", stacklevel=2) + # _, mode, fifths = analysis.estimate_key(note_array) + # key_sigs_by_track = {} + # global_key_sigs = [(0, fifths_mode_to_key_name(fifths, mode))] + # + # if assign_note_ids: + # note_ids = ["n{}".format(i) for i in range(len(note_array))] + # else: + # note_ids = [None for i in range(len(note_array))] + # + # time_sigs_by_part = defaultdict(set) + # for tr, ts_list in time_sigs_by_track.items(): + # for ts in ts_list: + # for part in track_to_part_mapping[tr]: + # time_sigs_by_part[part].add(ts) + # for ts in global_time_sigs: + # for part in set(part for _, part, _ in group_part_voice_keys): + # time_sigs_by_part[part].add(ts) + # + # key_sigs_by_part = defaultdict(set) + # for tr, ks_list in key_sigs_by_track.items(): + # for ks in ks_list: + # for part in track_to_part_mapping[tr]: + # key_sigs_by_part[part].add(ks) + # for ks in global_key_sigs: + # for part in set(part for _, part, _ in group_part_voice_keys): + # key_sigs_by_part[part].add(ks) + # + # # names_by_part = defaultdict(set) + # # for tr_ch, pg_p_v in zip(tr_ch_keys, group_part_voice_keys): + # # print(tr_ch, pg_p_v) + # # for tr, name in track_names_by_track.items(): + # # print(tr, track_to_part_mapping, name) + # # for part in track_to_part_mapping[tr]: + # # names_by_part[part] = name + # + # notes_by_part = defaultdict(list) + # for (part, voice), note, spelling, note_id in zip( + # part_voice_list, note_list, spelling_global, note_ids + # ): + # notes_by_part[part].append((note, voice, spelling, note_id)) + # + # partlist = [] + # part_to_part_group = dict((p, pg) for pg, p, _ in group_part_voice_keys) + # part_groups = {} + # for part_nr, note_info in notes_by_part.items(): + # notes, voices, spellings, note_ids = zip(*note_info) + # part = create_part( + # divs, + # notes, + # spellings, + # voices, + # note_ids, + # sorted(time_sigs_by_part[part_nr]), + # sorted(key_sigs_by_part[part_nr]), + # part_id="P{}".format(part_nr + 1), + # part_name=part_names.get(part_nr, None), + # ) + # + # # print(part.pretty()) + # # if this part has an associated part_group number we create a PartGroup + # # if necessary, and add the part to that. The newly created PartGroup is + # # then added to the partlist. + # pg_nr = part_to_part_group[part_nr] + # if pg_nr is None: + # partlist.append(part) + # else: + # if pg_nr not in part_groups: + # part_groups[pg_nr] = score.PartGroup( + # group_name=group_names.get(pg_nr, None) + # ) + # partlist.append(part_groups[pg_nr]) + # part_groups[pg_nr].children.append(part) + # + # # add tempos to first part + # part = next(score.iter_parts(partlist)) + # for t, qpm in global_tempos: + # part.add(score.Tempo(qpm, unit="q"), t) + # + # if not ensure_list and len(partlist) == 1: + # return partlist[0] + # else: + # return partlist def make_track_to_part_mapping(tr_ch_keys, group_part_voice_keys): diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index 18b771c6..efef59af 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -101,7 +101,7 @@ def create_part( return part -def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", assign_note_ids: bool = True, +def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: int = None, assign_note_ids: bool = True, ensurelist: bool = False, estimate_key: bool = False) -> Union[Union[Part, PartGroup], list]: """ A generic function to transform an enriched note_array to part. @@ -121,7 +121,8 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", assign_n Parameters ---------- note_array : structure array or list of structured arrays - + divs : int (optional) + Necessary Provided divs for midi import. assign_note_ids : bool (optional) Assign note_ids ensurelist: bool (optional) @@ -133,8 +134,6 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", assign_n part : list or Part or PartGroup Maybe should return score """ - - if isinstance(note_array, list): parts = [ note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for @@ -188,12 +187,13 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", assign_n estimate_voice_info = False part_voice_list = note_array["voice"] else: - part_voice_list = np.empty(len(note_array)) + estimate_voice_info = True + part_voice_list = np.full(len(note_array), np.inf) if not all(x in dtypes for x in ['step', 'alter', 'octave']): warnings.warn("pitch spelling") spelling_global = analysis.estimate_spelling(note_array) - note_array = rfn.merge_arrays((note_array, spelling_global)) + note_array = rfn.merge_arrays((note_array, spelling_global), flatten=True) if estimate_voice_info: warnings.warn("voice estimation", stacklevel=2) @@ -202,8 +202,9 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", assign_n estimated_voices = analysis.estimate_voices(note_array) assert len(part_voice_list) == len(estimated_voices) for part_voice, voice_est in zip(part_voice_list, estimated_voices): - if part_voice[1] is None: - part_voice[1] = voice_est + if part_voice == np.inf: + part_voice = voice_est + note_array = rfn.merge_arrays((note_array, np.array(estimated_voices, dtype=[("voice", int)])), flatten=True) if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): warnings.warn("key estimation", stacklevel=2) @@ -219,8 +220,8 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", assign_n ks_end_times = np.r_[global_key_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] global_key_sigs = np.column_stack((global_key_sigs, ks_end_times)) - - divs = int((note_array[0]["duration_div"] / note_array[0]["duration_beat"])*(note_array[0]["ts_beat_type"]/4)) + if divs is None: + divs = int((note_array[0]["duration_div"] / note_array[0]["duration_beat"])*(note_array[0]["ts_beat_type"]/4)) part = create_part( ticks=divs, note_array=note_array, From ac03ab0396b64dc1b3d36bec2c0dc2c74049e146 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 29 Aug 2022 14:27:40 +0200 Subject: [PATCH 009/122] reassign ids when id is not a valid field in the note_array. --- partitura/musicanalysis/note_array_to_part.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index efef59af..62c79361 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -145,7 +145,10 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in if len(note_array) == 0: raise ValueError("The note array is empty.") - if assign_note_ids or np.all(note_array["id"] == note_array["id"][0]): + if "id" not in note_array.dtype.names: + note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] + note_array = rfn.merge_arrays((note_array, np.array(note_ids, dtype=[("id", str)])), flatten=True) + elif assign_note_ids or np.all(note_array["id"] == note_array["id"][0]): note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] note_array["id"] = np.array(note_ids) @@ -201,9 +204,10 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in # Zero duration notes are currently deleted estimated_voices = analysis.estimate_voices(note_array) assert len(part_voice_list) == len(estimated_voices) - for part_voice, voice_est in zip(part_voice_list, estimated_voices): - if part_voice == np.inf: - part_voice = voice_est + for i, (part_voice, voice_est) in enumerate(zip(part_voice_list, estimated_voices)): + # Not sure if correct. + if part_voice != np.inf: + estimated_voices[i] = part_voice note_array = rfn.merge_arrays((note_array, np.array(estimated_voices, dtype=[("voice", int)])), flatten=True) if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): @@ -229,4 +233,7 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in part_id=part_id, part_name=None, ) - return part \ No newline at end of file + if ensurelist: + return [part] + else: + return part \ No newline at end of file From 5efcca4070bb4a0eb547ec8192e3b953e2e359fa Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 29 Aug 2022 15:12:34 +0200 Subject: [PATCH 010/122] Correct type of fractions. --- partitura/musicanalysis/note_array_to_part.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index 62c79361..0c5f7f66 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -10,10 +10,10 @@ def create_divs_from_beats(note_array): - duration_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["duration_beat"]] - onset_fractions = [Fraction(ix).limit_denominator(256) for ix in note_array["onset_beat"]] + duration_fractions = [Fraction(float(ix)).limit_denominator(256) for ix in note_array["duration_beat"]] + onset_fractions = [Fraction(float(ix)).limit_denominator(256) for ix in note_array["onset_beat"]] divs = np.lcm.reduce( - [Fraction(ix).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) + [Fraction(float(ix)).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) From 39fc4206c82ffc81ba4d0fe5185b43c4323f0d97 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 29 Aug 2022 15:51:10 +0200 Subject: [PATCH 011/122] fix for scores starting with grace note. --- partitura/musicanalysis/note_array_to_part.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index 0c5f7f66..f11af513 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -224,8 +224,13 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in ks_end_times = np.r_[global_key_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] global_key_sigs = np.column_stack((global_key_sigs, ks_end_times)) + # compute quarter divs if divs is None: - divs = int((note_array[0]["duration_div"] / note_array[0]["duration_beat"])*(note_array[0]["ts_beat_type"]/4)) + # find first note with nonzero duration (in case score starts with grace_note). + for idx, dur in enumerate(note_array["duration_beat"]): + if dur != 0: + break + divs = int((note_array[idx]["duration_div"] / note_array[idx]["duration_beat"])*(note_array[idx]["ts_beat_type"]/4)) part = create_part( ticks=divs, note_array=note_array, From a085888a77c44e80d047d026dec3a7857d09c409 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Fri, 16 Sep 2022 18:11:37 +0200 Subject: [PATCH 012/122] starting the music21 importer --- partitura/io/importmusic21.py | 124 ++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 partitura/io/importmusic21.py diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py new file mode 100644 index 00000000..b63d9b7f --- /dev/null +++ b/partitura/io/importmusic21.py @@ -0,0 +1,124 @@ +import partitura as pt +import numpy as np +from fractions import Fraction + +from partitura.utils import generic +try: + import music21 as m21 +except ImportError: + m21 = None + +def load_music21(mei_path: str) -> list: + """ + Loads a Mei score from path and returns a list of Partitura.Part + + Parameters + ---------- + mei_path : str + The path to an MEI score. + + Returns + ------- + part_list : list + A list of Partitura Part or GroupPart Objects. + """ + if m21 is None: + raise ImportError("Music21 must be installed for this function to work") + + parser = M21Parser(mei_path) + # create parts from the specifications in the mei + parser.create_parts() + # fill parts with the content from the mei + parser.fill_parts() + return parser.parts + + +class M21Parser: + + def __init__(self, m21_score): + self.m21_score = m21_score + self.ppq = self.find_ppq() + + def create_parts(self): + # create the part list + self.parts = [pt.score.Part(m21_part.id, m21_part.partName, quarter_duration=self.ppq) for m21_part in self.m21_score.parts] + + def fill_parts(self): + # fill parts with the content of the score + for part_idx, m21_part, pt_part in enumerate(zip(self.m21_score.parts, self.parts)): + # fill notes + self.fill_part_notes(m21_part,pt_part, part_idx) + # fill rests + self.fill_part_rests(self, m21_part,pt_part, part_idx) + + def fill_part_rests(self, m21_part,pt_part, part_idx): + for rest in m21_part.recurse().getElementsByClass(m21.note.Rest): + rest = pt.score.Rest( + id=rest.id, + voice=self.find_voice(rest), + staff=part_idx + 1, + symbolic_duration=rest.duration.type, + articulations=None, + ) + # add rest to the part + position = int(rest.getOffsetInHierarchy(self.m21_score)*self.ppq) + duration = int(rest.duration.quarterLength*self.ppq) + pt_part.add(rest, position, position + duration) + + def fill_part_notes(self, m21_part,pt_part, part_idx): + for generic_note in m21_part.recurse().notes: + for pitch in generic_note.pitches: + if generic_note.duration.is_grace: + note = pt.score.GraceNote( + grace_type="acciaccatura" if generic_note.duration.slash else "appoggiatura", + step=pitch.step, + octave=pitch.octave, + alter=pitch.accidental.alter if pitch.accidental is not None else None, + id=pitch.id, + voice=self.find_voice(generic_note), + staff=part_idx + 1, + symbolic_duration=generic_note.duration.type, + articulations=None, # TODO : add articulation + ) + else: + note= pt.score.Note( + step= pitch.step, + octave= pitch.octave, + alter=pitch.accidental.alter if pitch.accidental is not None else None, + id=pitch.id, + voice= self.find_voice(generic_note), + staff= part_idx + 1, + symbolic_duration=generic_note.duration.type, + articulations=None, # TODO : add articulation + ) + position = int(generic_note.getOffsetInHierarchy(self.m21_score)*self.ppq) + duration = int(generic_note.duration.quarterLength*self.ppq) + pt_part.add(note, position, position + duration) + + def find_voice(self, m21_general_note): + """Return the voice for an music21 general note""" + return 1 if type(m21_general_note.activeSite) is m21.stream.Measure else m21_general_note.activeSite.id + + def find_staff(self, m21_general_note): + """Return the staff for an music21 general note""" + return [e for e in m21_general_note.containerHierarchy() if type(e) is m21.score.Staff][0]. + + + def find_ppq(self): + """Finds the ppq """ + + def fractional_gcd(inputs): + denoms = np.array([Fraction(e).denominator for e in inputs],dtype = int) + denoms_lcm = np.lcm.reduce(denoms) + return Fraction(1,denoms_lcm) + + durs = [el.duration.quarterLength for el in self.m21_score.recurse().getElementsByClass('Music21Object') if el.duration.quarterLength!=0 ] + return fractional_gcd(durs).denominator + + + + + + + + From afacade0a2e63a2df8fcef119f386e497827538f Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Mon, 19 Sep 2022 15:40:58 +0200 Subject: [PATCH 013/122] working parser with notes, ts, ks, clefs --- partitura/__init__.py | 1 + partitura/io/__init__.py | 1 + partitura/io/importmusic21.py | 57 ++-- tests/__init__.py | 2 + tests/data/musicxml/test_clefs_tss.xml | 399 ++++++++++++++++++++++++ tests/data/musicxml/test_grace_note.xml | 199 ++++++++++++ tests/test_m21_import.py | 59 ++++ tests/test_mei.py | 2 +- 8 files changed, 701 insertions(+), 19 deletions(-) create mode 100644 tests/data/musicxml/test_clefs_tss.xml create mode 100644 tests/data/musicxml/test_grace_note.xml create mode 100644 tests/test_m21_import.py diff --git a/partitura/__init__.py b/partitura/__init__.py index f256985d..f2e5f5bc 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -18,6 +18,7 @@ from .io.exportmatch import save_match from .io.importnakamura import load_nakamuramatch, load_nakamuracorresp from .io.exportparangonada import save_csv_for_parangonada +from .io.importmusic21 import load_music21 from .display import render from . import musicanalysis diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index e11b2f3a..f90aaf86 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -4,6 +4,7 @@ from .importmatch import load_match from .importmei import load_mei from .importkern import load_kern +from .importmusic21 import load_music21 class NotSupportedFormatError(Exception): diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py index b63d9b7f..343cd6a0 100644 --- a/partitura/io/importmusic21.py +++ b/partitura/io/importmusic21.py @@ -45,36 +45,42 @@ def create_parts(self): def fill_parts(self): # fill parts with the content of the score - for part_idx, m21_part, pt_part in enumerate(zip(self.m21_score.parts, self.parts)): + for part_idx, (m21_part, pt_part) in enumerate(zip(self.m21_score.parts, self.parts)): # fill notes self.fill_part_notes(m21_part,pt_part, part_idx) # fill rests - self.fill_part_rests(self, m21_part,pt_part, part_idx) + self.fill_part_rests(m21_part,pt_part, part_idx) + # fill key signatures + self.fill_part_ks(m21_part,pt_part, part_idx) + # fill time signatures + self.fill_part_ts(m21_part,pt_part, part_idx) + # fill with clefs + self.fill_part_clefs(m21_part,pt_part, part_idx) def fill_part_rests(self, m21_part,pt_part, part_idx): - for rest in m21_part.recurse().getElementsByClass(m21.note.Rest): - rest = pt.score.Rest( - id=rest.id, - voice=self.find_voice(rest), + for m21_rest in m21_part.recurse().getElementsByClass(m21.note.Rest): + pt_rest = pt.score.Rest( + id=m21_rest.id, + voice=self.find_voice(m21_rest), staff=part_idx + 1, - symbolic_duration=rest.duration.type, + symbolic_duration=m21_rest.duration.type, articulations=None, ) # add rest to the part - position = int(rest.getOffsetInHierarchy(self.m21_score)*self.ppq) - duration = int(rest.duration.quarterLength*self.ppq) - pt_part.add(rest, position, position + duration) + position = int(m21_rest.getOffsetBySite(self.m21_score.recurse())*self.ppq) + duration = int(m21_rest.duration.quarterLength*self.ppq) + pt_part.add(pt_rest, position, position + duration) def fill_part_notes(self, m21_part,pt_part, part_idx): for generic_note in m21_part.recurse().notes: - for pitch in generic_note.pitches: - if generic_note.duration.is_grace: + for i_pitch, pitch in enumerate(generic_note.pitches): + if generic_note.duration.isGrace: note = pt.score.GraceNote( grace_type="acciaccatura" if generic_note.duration.slash else "appoggiatura", step=pitch.step, octave=pitch.octave, alter=pitch.accidental.alter if pitch.accidental is not None else None, - id=pitch.id, + id=generic_note.id + "_{}".format(i_pitch), voice=self.find_voice(generic_note), staff=part_idx + 1, symbolic_duration=generic_note.duration.type, @@ -85,7 +91,7 @@ def fill_part_notes(self, m21_part,pt_part, part_idx): step= pitch.step, octave= pitch.octave, alter=pitch.accidental.alter if pitch.accidental is not None else None, - id=pitch.id, + id="{}_{}".format(generic_note.id,i_pitch), voice= self.find_voice(generic_note), staff= part_idx + 1, symbolic_duration=generic_note.duration.type, @@ -94,15 +100,30 @@ def fill_part_notes(self, m21_part,pt_part, part_idx): position = int(generic_note.getOffsetInHierarchy(self.m21_score)*self.ppq) duration = int(generic_note.duration.quarterLength*self.ppq) pt_part.add(note, position, position + duration) + + def fill_part_ts(self, m21_part,pt_part, part_idx): + for ts in m21_part.recurse().getElementsByClass(m21.meter.TimeSignature): + new_time_signature = pt.score.TimeSignature(ts.numerator, ts.denominator) + position = int(ts.getOffsetInHierarchy(self.m21_score)*self.ppq) + pt_part.add(new_time_signature, position) + + def fill_part_ks(self, m21_part,pt_part, part_idx): + for ks in m21_part.recurse().getElementsByClass(m21.key.KeySignature): + new_key_signature = pt.score.KeySignature(ks.sharps, None) + position = int(ks.getOffsetInHierarchy(self.m21_score)*self.ppq) + pt_part.add(new_key_signature, position) + + def fill_part_clefs(self, m21_part,pt_part, part_idx): + for m21_clef in m21_part.recurse().getElementsByClass(m21.clef.Clef): + pt_clef = pt.score.Clef(int(part_idx) +1 , m21_clef.sign, int(m21_clef.line), m21_clef.octaveChange) + position = int(m21_clef.getOffsetInHierarchy(self.m21_score)*self.ppq) + pt_part.add(pt_clef, position) + def find_voice(self, m21_general_note): """Return the voice for an music21 general note""" return 1 if type(m21_general_note.activeSite) is m21.stream.Measure else m21_general_note.activeSite.id - def find_staff(self, m21_general_note): - """Return the staff for an music21 general note""" - return [e for e in m21_general_note.containerHierarchy() if type(e) is m21.score.Staff][0]. - def find_ppq(self): """Finds the ppq """ diff --git a/tests/__init__.py b/tests/__init__.py index 0280a48b..c9f8685e 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -13,6 +13,7 @@ KERN_PATH = os.path.join(DATA_PATH, "kern") MATCH_PATH = os.path.join(DATA_PATH, "match") NAKAMURA_PATH = os.path.join(DATA_PATH, "nakamura") +M21_PATH = DATA_PATH # this is a list of files for which importing and subsequent exporting should # yield identical MusicXML @@ -143,3 +144,4 @@ KERN_TIES = [os.path.join(KERN_PATH, fn) for fn in ["tie_mismatch.krn"]] +M21_TESTFILES = [os.path.join(DATA_PATH, "musicxml","test_clefs_tss.xml"), os.path.join(DATA_PATH, "musicxml","test_grace_note.xml")] diff --git a/tests/data/musicxml/test_clefs_tss.xml b/tests/data/musicxml/test_clefs_tss.xml new file mode 100644 index 00000000..40b238f1 --- /dev/null +++ b/tests/data/musicxml/test_clefs_tss.xml @@ -0,0 +1,399 @@ + + + + + test clefs time signatures + + + + MuseScore 3.6.2 + 2021-11-04 + + + + + + + + + + 7 + 40 + + + 1697.14 + 1200 + + 85.7143 + 85.7143 + 85.7143 + 85.7143 + + + 85.7143 + 85.7143 + 85.7143 + 85.7143 + + + + + + + title + test clefs time signatures + + + + bracket + + + Violin I + + Violin + + + + 1 + 41 + 78.7402 + 0 + + + + Violin II + + Violin + + + + 4 + 41 + 78.7402 + 0 + + + + Viola + + Viola + + + + 7 + 42 + 78.7402 + 0 + + + + Violoncello + + Violoncello + + + + 11 + 43 + 78.7402 + 0 + + + + + + + + + + 118.24 + 0.00 + + 170.00 + + + + 1 + + 2 + + + + G + 2 + + + + + A + 4 + + 1 + 1 + quarter + up + + + + B + 4 + + 1 + 1 + quarter + down + + + + C + 1 + 5 + + 2 + 1 + half + down + + + + + + 4 + + + + + 4 + 1 + + + + + + 4 + 1 + + + light-heavy + + + + + + + + 96.19 + + + + 1 + + 2 + + + + G + 2 + + + + + 4 + 1 + + + + + + 4 + + + + + 4 + 1 + + + + + + 4 + 1 + + + light-heavy + + + + + + + + 96.19 + + + + 1 + + 2 + + + + C + 3 + + + + + 4 + 1 + + + + + + 4 + + + + + 4 + 1 + + + + + + F + 4 + + + + + 4 + 1 + + + light-heavy + + + + + + + + 96.19 + + + + 1 + + 2 + + + + F + 4 + + + + + D + 3 + + 2 + 1 + half + down + + + + F + 1 + 3 + + 2 + 1 + half + down + + + + + + 4 + + + G + 2 + -1 + + + + + B + 2 + + 4 + 1 + whole + + + 4 + + + + G + 1 + 2 + + 1 + 2 + quarter + down + + + + E + 3 + + 1 + 2 + quarter + down + + + + 2 + 2 + half + + + + + + B + 3 + + 4 + 1 + whole + + + light-heavy + + + + diff --git a/tests/data/musicxml/test_grace_note.xml b/tests/data/musicxml/test_grace_note.xml new file mode 100644 index 00000000..a5ad3ec6 --- /dev/null +++ b/tests/data/musicxml/test_grace_note.xml @@ -0,0 +1,199 @@ + + + + + test grace note + + + + MuseScore 3.6.2 + 2021-11-04 + + + + + + + + + + 7 + 40 + + + 1697.14 + 1200 + + 85.7143 + 85.7143 + 85.7143 + 85.7143 + + + 85.7143 + 85.7143 + 85.7143 + 85.7143 + + + + + + + title + test grace note + + + + brace + + + Piano + Pno. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + + + 50.00 + 0.00 + + 170.00 + + + + 1 + + -1 + + + + G + 2 + + + + + C + 4 + + 1 + 1 + quarter + up + + + + 1 + 1 + quarter + + + + + D + 4 + + 1 + eighth + up + + + + E + 4 + + 1 + 1 + quarter + up + + + + 1 + 1 + quarter + + + + + + + G + 4 + + 1 + 16th + up + begin + begin + + + + + A + 4 + + 1 + 16th + up + continue + continue + + + + + A + -1 + 4 + + 1 + 16th + flat + up + end + end + + + + G + 4 + + 1 + 1 + quarter + up + + + + 1 + 1 + quarter + + + + 2 + 1 + half + + + light-heavy + + + + diff --git a/tests/test_m21_import.py b/tests/test_m21_import.py new file mode 100644 index 00000000..fac4a239 --- /dev/null +++ b/tests/test_m21_import.py @@ -0,0 +1,59 @@ +""" +This file contains test functions for music21 import +""" + +import unittest + + +from tests import M21_TESTFILES +from partitura import load_music21, load_mei, EXAMPLE_MEI +import partitura.score as score +from partitura.io.importmei import MeiParser +from partitura.utils import compute_pianoroll +from lxml import etree +from xmlschema.names import XML_NAMESPACE +import music21 as m21 + +import numpy as np +from pathlib import Path + + +class TestImportMEI(unittest.TestCase): + + def test_grace_note(self): + m21_score = m21.converter.parse(M21_TESTFILES[1]) + part_list = load_music21(m21_score) + part = list(score.iter_parts(part_list))[0] + grace_notes = list(part.iter_all(score.GraceNote)) + self.assertTrue(len(part.note_array()) == 7) + self.assertTrue(len(grace_notes) == 4) + self.assertTrue(grace_notes[0].grace_type == "acciaccatura") + self.assertTrue(grace_notes[1].grace_type == "appoggiatura") + + def test_clef(self): + m21_score = m21.converter.parse(M21_TESTFILES[0]) + part_list = load_music21(m21_score) + # test on part 2 + part2 = list(score.iter_parts(part_list))[2] + clefs2 = list(part2.iter_all(score.Clef)) + self.assertTrue(len(clefs2) == 2) + self.assertTrue(clefs2[0].start.t == 0) + self.assertTrue(clefs2[0].sign == "C") + self.assertTrue(clefs2[0].line == 3) + self.assertTrue(clefs2[0].staff == 3) + self.assertTrue(clefs2[0].octave_change == 0) + self.assertTrue(clefs2[1].start.t == 8) + self.assertTrue(clefs2[1].sign == "F") + self.assertTrue(clefs2[1].line == 4) + self.assertTrue(clefs2[1].staff == 3) + self.assertTrue(clefs2[1].octave_change == 0) + # test on part 3 + part3 = list(score.iter_parts(part_list))[3] + clefs3 = list(part3.iter_all(score.Clef)) + self.assertTrue(len(clefs3) == 2) + self.assertTrue(clefs3[0].start.t == 0) + self.assertTrue(clefs3[1].start.t == 4) + self.assertTrue(clefs3[1].sign == "G") + self.assertTrue(clefs3[1].line == 2) + self.assertTrue(clefs3[1].staff == 4) + self.assertTrue(clefs3[1].octave_change == -1) \ No newline at end of file diff --git a/tests/test_mei.py b/tests/test_mei.py index f0e1896a..53a9364d 100644 --- a/tests/test_mei.py +++ b/tests/test_mei.py @@ -1,5 +1,5 @@ """ -This file contains test functions for MEI export +This file contains test functions for MEI import """ import unittest From 0898eaf5da2e830fec48600e7c8bd00c09855941 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Mon, 19 Sep 2022 16:06:27 +0200 Subject: [PATCH 014/122] removed the grace type test, since music21 don't import it correctly --- partitura/io/importmusic21.py | 2 +- tests/test_m21_import.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py index 343cd6a0..15984b98 100644 --- a/partitura/io/importmusic21.py +++ b/partitura/io/importmusic21.py @@ -80,7 +80,7 @@ def fill_part_notes(self, m21_part,pt_part, part_idx): step=pitch.step, octave=pitch.octave, alter=pitch.accidental.alter if pitch.accidental is not None else None, - id=generic_note.id + "_{}".format(i_pitch), + id= "{}_{}".format(generic_note.id,i_pitch), voice=self.find_voice(generic_note), staff=part_idx + 1, symbolic_duration=generic_note.duration.type, diff --git a/tests/test_m21_import.py b/tests/test_m21_import.py index fac4a239..8ecd827f 100644 --- a/tests/test_m21_import.py +++ b/tests/test_m21_import.py @@ -27,8 +27,6 @@ def test_grace_note(self): grace_notes = list(part.iter_all(score.GraceNote)) self.assertTrue(len(part.note_array()) == 7) self.assertTrue(len(grace_notes) == 4) - self.assertTrue(grace_notes[0].grace_type == "acciaccatura") - self.assertTrue(grace_notes[1].grace_type == "appoggiatura") def test_clef(self): m21_score = m21.converter.parse(M21_TESTFILES[0]) From daf9e9831746e15909d6fc869a21afcd0bc05373 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Tue, 11 Oct 2022 15:46:50 +0200 Subject: [PATCH 015/122] Updates from latest develop changes. --- partitura/musicanalysis/note_array_to_part.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index f11af513..9d401190 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -33,7 +33,7 @@ def create_part( part.set_quarter_duration(0, ticks) clef = score.Clef( - number=1, **estimate_clef_properties(note_array["pitch"]) + staff=1, **estimate_clef_properties(note_array["pitch"]) ) part.add(clef, 0) for t_start, name, t_end in key_sigs: From f1c9436a3e213626495f72b07d3c63320c42419c Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 16 Dec 2022 16:13:07 +0100 Subject: [PATCH 016/122] correction for negative divs. --- partitura/musicanalysis/note_array_to_part.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index f11af513..fc319a6c 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -15,6 +15,8 @@ def create_divs_from_beats(note_array): divs = np.lcm.reduce( [Fraction(float(ix)).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) + if min(onset_divs) < 0: + onset_divs -= min(onset_divs) duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) @@ -33,7 +35,7 @@ def create_part( part.set_quarter_duration(0, ticks) clef = score.Clef( - number=1, **estimate_clef_properties(note_array["pitch"]) + staff=1, **estimate_clef_properties(note_array["pitch"]) ) part.add(clef, 0) for t_start, name, t_end in key_sigs: From c154c36d78e43b987df665aad18e3a30cbd636ce Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 16 Dec 2022 16:29:39 +0100 Subject: [PATCH 017/122] correction for negative divs. --- partitura/musicanalysis/note_array_to_part.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index fc319a6c..b27999b6 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -15,8 +15,9 @@ def create_divs_from_beats(note_array): divs = np.lcm.reduce( [Fraction(float(ix)).limit_denominator(256).denominator for ix in np.unique(note_array["duration_beat"])]) onset_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), onset_fractions)) - if min(onset_divs) < 0: - onset_divs -= min(onset_divs) + min_onset_div = min(onset_divs) + if min_onset_div < 0: + onset_divs = list(map(lambda x: x - min_onset_div, onset_divs)) duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) From f63e3c8c8e3e134e42613f7d4fea9f7a71b47336 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 17 Feb 2023 10:32:06 +0100 Subject: [PATCH 018/122] fux for leftOutTied notes in new match file format. --- partitura/io/importmatch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index c384a40d..356b2d6a 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -591,7 +591,9 @@ def part_from_matchfile( if "accent" in note.ScoreAttributesList: articulations.add("accent") if "leftOutTied" in note.ScoreAttributesList: - continue + # continue introduces inconsistency when save_match + # continue + pass # dictionary with keyword args with which the Note # (or GraceNote) will be instantiated From c3603193a3c05bba5438862abcf013ba2720465c Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 17 Feb 2023 10:35:34 +0100 Subject: [PATCH 019/122] Part and Ppart to scorelike and performance like. --- partitura/musicanalysis/performance_codec.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index bb022b69..7deaf806 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -6,6 +6,8 @@ """ import numpy as np import numpy.lib.recfunctions as rfn +from partitura.score import ScoreLike +from partitura.performance import PerformanceLike try: import torch @@ -28,8 +30,8 @@ def __init__(self): def encode_performance( - part: Part, - ppart: PerformedPart, + part: ScoreLike, + ppart: PerformanceLike, alignment: list, return_u_onset_idx=False, ): @@ -85,7 +87,7 @@ def encode_performance( def decode_performance( - part: Part, + part: ScoreLike, performance_array: np.ndarray, snote_ids=None, part_id=None, From ecc794bda0b262b61a6d25d181ffce7632dee42d Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 17 Feb 2023 18:26:13 +0100 Subject: [PATCH 020/122] Correction for note features of fixed length. --- partitura/musicanalysis/note_features.py | 245 ++++++++++++++++------- 1 file changed, 178 insertions(+), 67 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index 58d0f9d0..1b71bcd8 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -12,6 +12,7 @@ import types from typing import List, Union, Tuple from partitura.utils import ensure_notearray, ensure_rest_array +from partitura.score import ScoreLike __all__ = [ "list_note_feats_functions", @@ -72,9 +73,10 @@ def list_note_feats_functions(): def make_note_features( - part: Union[score.Part, score.PartGroup, List], + part: ScoreLike, feature_functions: Union[List, str], add_idx: bool = False, + include_empty_features: bool = True ) -> Tuple[np.ndarray, List]: """Compute the specified feature functions for a part. @@ -94,13 +96,18 @@ def make_note_features( Parameters ---------- - part : Part + score : Part The score as a Part instance feature_functions : list or str A list of feature functions. Elements of the list can be either the functions themselves or the names of a feature function as strings (or a mix), or the keywork "all". The feature functions specified by name are looked up in the `featuremixer.featurefunctions` module. + add_idx : bool (default: False) + If True, the index of the note in the part is added as a + feature. This is useful for debugging. + include_empty_features : bool (default: True) + If True, features that are empty are included in the output. Returns ------- @@ -137,7 +144,7 @@ def make_note_features( func = bf else: warnings.warn("Ignoring unknown feature function {}".format(bf)) - bf, bn = func(na, part) + bf, bn = func(na, part, include_empty_features=include_empty_features) # check if the size and number of the feature function are correct if bf.size != 0: if bf.shape[1] != len(bn): @@ -303,7 +310,7 @@ def make_rest_features( def compute_note_array( - part, + part : ScoreLike, include_pitch_spelling=False, include_key_signature=False, include_time_signature=False, @@ -401,7 +408,7 @@ def full_note_array(part): ) -def polynomial_pitch_feature(na, part): +def polynomial_pitch_feature(na, part, **kwargs): """Normalize pitch feature.""" pitches = na["pitch"].astype(float) feature_names = ["pitch"] @@ -410,7 +417,7 @@ def polynomial_pitch_feature(na, part): return np.expand_dims(W, axis=1), feature_names -def duration_feature(na, part): +def duration_feature(na, part, **kwargs): """Duration feature. Parameters @@ -426,7 +433,7 @@ def duration_feature(na, part): return W, feature_names -def onset_feature(na, part): +def onset_feature(na, part, **kwargs): """Onset feature Returns: @@ -446,12 +453,12 @@ def onset_feature(na, part): return W, feature_names -def relative_score_position_feature(na, part): - W, names = onset_feature(na, part) +def relative_score_position_feature(na, part, **kwargs): + W, names = onset_feature(na, part, **kwargs) return W[:, 1:], names[1:] -def grace_feature(na, part): +def grace_feature(na, part, **kwargs): """Grace feature. Returns: @@ -482,7 +489,7 @@ def grace_feature(na, part): return W, feature_names -def loudness_direction_feature(na, part): +def loudness_direction_feature(na, part, **kwargs): """The loudness directions in part. This function returns a varying number of descriptors, depending @@ -502,18 +509,42 @@ def loudness_direction_feature(na, part): onsets = na["onset_div"] N = len(onsets) + constant = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff", "unknown_constant"] + impulsive = ["fp", "sf", "sfp", "sfz", "unknown_impulsive"] + names = constant + impulsive + ["loudness_incr", "loudness_decr"] directions = list(part.iter_all(score.LoudnessDirection, include_subclasses=True)) + if "include_empty_features" in kwargs.keys(): + force_size = kwargs["include_empty_features"] + else: + force_size = False + if force_size: + def to_name(d): + if isinstance(d, score.ConstantLoudnessDirection): + if d.text in constant: + return d.text + else: + return "unknown_constant" + elif isinstance(d, score.ImpulsiveLoudnessDirection): + if d.text in impulsive: + return d.text + else: + return "unknown_impulsive" + elif isinstance(d, score.IncreasingLoudnessDirection): + return "loudness_incr" + elif isinstance(d, score.DecreasingLoudnessDirection): + return "loudness_decr" + else: + def to_name(d): + if isinstance(d, score.ConstantLoudnessDirection): + return d.text + elif isinstance(d, score.ImpulsiveLoudnessDirection): + return d.text + elif isinstance(d, score.IncreasingLoudnessDirection): + return "loudness_incr" + elif isinstance(d, score.DecreasingLoudnessDirection): + return "loudness_decr" - def to_name(d): - if isinstance(d, score.ConstantLoudnessDirection): - return d.text - elif isinstance(d, score.ImpulsiveLoudnessDirection): - return d.text - elif isinstance(d, score.IncreasingLoudnessDirection): - return "loudness_incr" - elif isinstance(d, score.DecreasingLoudnessDirection): - return "loudness_decr" feature_by_name = {} for d in directions: @@ -522,16 +553,20 @@ def to_name(d): ) bf += feature_function_activation(d)(onsets) - W = np.empty((len(onsets), len(feature_by_name))) - names = [None] * len(feature_by_name) + if not force_size: + names = [None] * len(feature_by_name) + W = np.zeros((len(onsets), len(names))) for name, (j, bf) in feature_by_name.items(): + if force_size: + j = names.index(name) + else: + names[j] = name W[:, j] = bf - names[j] = name return W, names -def tempo_direction_feature(na, part): +def tempo_direction_feature(na, part, **kwargs): """The tempo directions in part. This function returns a varying number of descriptors, depending @@ -545,22 +580,52 @@ def tempo_direction_feature(na, part): """ onsets = na["onset_div"] N = len(onsets) - + constant = ["adagio", "largo", "lento", "grave", "largo", "lento", "grave", "lento", + "larghetto", "adagietto", "andante", "andantino", "moderato", "allegretto", + "allegro", "vivace", "presto", "prestissimo", "tempo_incr", "tempo_decr", + "unknown_constant"] + names = constant + ["tempo_incr", "tempo_decr"] directions = list(part.iter_all(score.TempoDirection, include_subclasses=True)) - def to_name(d): - if isinstance(d, score.ResetTempoDirection): - ref = d.reference_tempo - if ref: - return ref.text - else: + + if "include_empty_features" in kwargs.keys(): + force_size = kwargs["include_empty_features"] + else: + force_size = False + if force_size: + def to_name(d): + if isinstance(d, score.ResetTempoDirection): + ref = d.reference_tempo + if ref: + if ref.text in constant: + return ref.text + else: + return "unknown_constant" + else: + return d.text + elif isinstance(d, score.ConstantTempoDirection): + if d.text in constant: + return d.text + else: + return "unknown_constant" + elif isinstance(d, score.IncreasingTempoDirection): + return "tempo_incr" + elif isinstance(d, score.DecreasingTempoDirection): + return "tempo_decr" + else: + def to_name(d): + if isinstance(d, score.ResetTempoDirection): + ref = d.reference_tempo + if ref: + return ref.text + else: + return d.text + elif isinstance(d, score.ConstantTempoDirection): return d.text - elif isinstance(d, score.ConstantTempoDirection): - return d.text - elif isinstance(d, score.IncreasingTempoDirection): - return "tempo_incr" - elif isinstance(d, score.DecreasingTempoDirection): - return "tempo_decr" + elif isinstance(d, score.IncreasingTempoDirection): + return "tempo_incr" + elif isinstance(d, score.DecreasingTempoDirection): + return "tempo_decr" feature_by_name = {} for d in directions: @@ -569,16 +634,21 @@ def to_name(d): ) bf += feature_function_activation(d)(onsets) - W = np.empty((len(onsets), len(feature_by_name))) - names = [None] * len(feature_by_name) + + if not force_size: + names = [None] * len(feature_by_name) + W = np.zeros((len(onsets), len(names))) for name, (j, bf) in feature_by_name.items(): + if force_size: + j = names.index(name) + else: + names[j] = name W[:, j] = bf - names[j] = name return W, names -def articulation_direction_feature(na, part): +def articulation_direction_feature(na, part, **kwargs): """ """ onsets = na["onset_div"] N = len(onsets) @@ -586,9 +656,21 @@ def articulation_direction_feature(na, part): directions = list( part.iter_all(score.ArticulationDirection, include_subclasses=True) ) + constant_names = ["staccato", "tenuto", "accent", "marcato", "unknown_articulation"] - def to_name(d): - return d.text + if "include_empty_features" in kwargs.keys(): + force_size = kwargs["include_empty_features"] + else: + force_size = False + if force_size: + def to_name(d): + if d.text in constant_names: + return d.text + else: + return "unknown_direction" + else: + def to_name(d): + return d.text feature_by_name = {} @@ -598,12 +680,19 @@ def to_name(d): ) bf += feature_function_activation(d)(onsets) - W = np.empty((len(onsets), len(feature_by_name))) - names = [None] * len(feature_by_name) + if force_size: + W = np.zeros((len(onsets), len(constant_names))) + names = constant_names + else: + W = np.zeros((len(onsets), len(feature_by_name))) + names = [None] * len(feature_by_name) for name, (j, bf) in feature_by_name.items(): + if force_size: + j = names.index(name) + else: + names[j] = name W[:, j] = bf - names[j] = name return W, names @@ -683,7 +772,7 @@ def feature_function_activation(direction): return interp1d(x, y, bounds_error=False, fill_value=0) -def slur_feature(na, part): +def slur_feature(na, part, **kwargs): """Slur feature. Returns: @@ -711,7 +800,7 @@ def slur_feature(na, part): return W, names -def articulation_feature(na, part): +def articulation_feature(na, part, **kwargs): """Articulation feature. This feature returns articulation-related note annotations, such as accents, legato, and tenuto. @@ -741,6 +830,11 @@ def articulation_feature(na, part): "unstress", "soft-accent", ] + if "include_empty_features" in kwargs: + force_size = kwargs["include_empty_features"] + else: + force_size = False + feature_by_name = {} notes = part.notes_tied if not np.all(na["pitch"] == 0) else part.rests N = len(notes) @@ -753,18 +847,24 @@ def articulation_feature(na, part): ) bf[i] = 1 - M = len(feature_by_name) - W = np.empty((N, M)) - names = [None] * M + if force_size: + M = len(names) + else: + M = len(feature_by_name) + names = [None] * M + W = np.zeros((N, M)) for name, (j, bf) in feature_by_name.items(): + if force_size: + j = names.index(name) + else: + names[j] = name W[:, j] = bf - names[j] = name return W, names -def ornament_feature(na, part): +def ornament_feature(na, part, **kwargs): """Ornament feature. This feature returns ornamentation note annotations,such as trills. @@ -803,24 +903,34 @@ def ornament_feature(na, part): art, (len(feature_by_name), np.zeros(N)) ) bf[i] = 1 + if "include_empty_features" in kwargs.keys(): + fix_size = kwargs["include_empty_features"] + else: + fix_size = False + if fix_size: + M = len(names) + else: + M = len(feature_by_name) + names = [None] * M + W = np.zeros((N, M)) - M = len(feature_by_name) - W = np.empty((N, M)) - names = [None] * M for name, (j, bf) in feature_by_name.items(): + if fix_size: + j = names.index(name) + else: + names[j] = name W[:, j] = bf - names[j] = name return W, names -def staff_feature(na, part): +def staff_feature(na, part, **kwargs): """Staff feature""" names = ["staff"] notes = {n.id: n.staff for n in part.notes_tied} N = len(notes) - W = np.empty((N, 1)) + W = np.zeros((N, 1)) for i, n in enumerate(na): W[i, 0] = notes[n["id"]] @@ -837,7 +947,7 @@ def staff_feature(na, part): # return np.empty(len(W)), [] -def fermata_feature(na, part): +def fermata_feature(na, part, **kwargs): """Fermata feature. Returns: @@ -852,7 +962,7 @@ def fermata_feature(na, part): return W, names -def metrical_feature(na, part): +def metrical_feature(na, part, **kwargs): """Metrical feature This feature encodes the metrical position in the bar. For example @@ -892,7 +1002,7 @@ def metrical_feature(na, part): ) bf[i] = 1 - W = np.empty((len(notes), len(feature_by_name))) + W = np.zeros((len(notes), len(feature_by_name))) names = [None] * len(feature_by_name) for name, (j, bf) in feature_by_name.items(): W[:, j] = bf @@ -901,7 +1011,7 @@ def metrical_feature(na, part): return W, names -def metrical_strength_feature(na, part): +def metrical_strength_feature(na, part, **kwargs): """Metrical strength feature This feature encodes the beat phase (relative position of a note within @@ -920,11 +1030,12 @@ def metrical_strength_feature(na, part): W[:, 0] = np.divide(relod, totmd) # Onset Phase W[:, 1] = na["is_downbeat"].astype(float) W[:, 2][W[:, 0] == 0.5] = 1.00 - W[:, 3][np.nonzero(np.add(W[:, 1], W[:, 0]) == 1.00)] + W[:, 3][np.nonzero(np.add(W[:, 1], W[:, 0]) == 1.00)] = 1.00 + return W, names -def time_signature_feature(na, part): +def time_signature_feature(na, part, **kwargs): """TIme Signature feature This feature encodes the time signature of the note in two sets of one-hot vectors, a one hot encoding of number of beats and a one hot encoding of beat type @@ -958,7 +1069,7 @@ def time_signature_feature(na, part): return W, names -def vertical_neighbor_feature(na, part): +def vertical_neighbor_feature(na, part, **kwargs): """Vertical neighbor feature. Describes various aspects of simultaneously starting notes. @@ -981,7 +1092,7 @@ def vertical_neighbor_feature(na, part): "lowest_pitch", "pitch_range", ] - W = np.empty((len(na), len(names))) + W = np.zeros((len(na), len(names))) for i, n in enumerate(na): neighbors = na[np.where(na["onset_beat"] == n["onset_beat"])]["pitch"] max_pitch = np.max(neighbors) From f92e7b34346d2eaf01c4f1762469f2b3260d564e Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 20 Feb 2023 16:39:09 +0100 Subject: [PATCH 021/122] added merge parts for score. --- partitura/musicanalysis/performance_codec.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 7deaf806..69b6b008 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -6,7 +6,7 @@ """ import numpy as np import numpy.lib.recfunctions as rfn -from partitura.score import ScoreLike +from partitura.score import ScoreLike, Score, merge_parts from partitura.performance import PerformanceLike try: @@ -61,6 +61,9 @@ def encode_performance( List of unique onset ids. Returned only when return_u_onset_idx is set to True. """ + if isinstance(part, Score): + # TODO change note ids in alignment + part = merge_parts(part) m_score, snote_ids = to_matched_score(part, ppart, alignment) @@ -118,7 +121,8 @@ def decode_performance( alignment: list (optional) A list of dicts for the alignment. """ - + if isinstance(part, Score): + part = merge_parts(part) snotes = part.notes_tied if snote_ids is None: From 7278b9c95d3cd49102f4170f4d3f41d83d6e49d4 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 20 Feb 2023 17:11:08 +0100 Subject: [PATCH 022/122] minor correction in note_features tempo_direction_feature. --- partitura/musicanalysis/note_features.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index 1b71bcd8..c009d60e 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -602,7 +602,10 @@ def to_name(d): else: return "unknown_constant" else: - return d.text + if d.text in constant: + return d.text + else: + return "unknown_constant" elif isinstance(d, score.ConstantTempoDirection): if d.text in constant: return d.text From 497b7c25222542a306ed83f6ce8f34f0263f5574 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Fri, 5 May 2023 12:19:23 +0200 Subject: [PATCH 023/122] first version of performance attributes --- partitura/musicanalysis/__init__.py | 2 + partitura/musicanalysis/performance_codec.py | 2 + .../musicanalysis/performance_expressions.py | 356 ++++++++++++++++++ 3 files changed, 360 insertions(+) create mode 100644 partitura/musicanalysis/performance_expressions.py diff --git a/partitura/musicanalysis/__init__.py b/partitura/musicanalysis/__init__.py index 718b278a..84d3d714 100644 --- a/partitura/musicanalysis/__init__.py +++ b/partitura/musicanalysis/__init__.py @@ -21,6 +21,7 @@ make_rest_features, ) from .performance_codec import encode_performance, decode_performance +from .performance_expressions import get_performance_expressions __all__ = [ @@ -36,4 +37,5 @@ "decode_performance", "compute_note_array", "full_note_array", + "get_performance_expressions" ] diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 58efbb7a..ee6e0a88 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -684,6 +684,7 @@ def to_matched_score(score: ScoreLike, n_dur = max(n["duration_sec"], 60 / 200 * 0.25) pair_info = (sn_on, sn_dur, sn['pitch'], n["onset_sec"], n_dur, n["velocity"]) if include_score_markings: + pair_info += (sn['voice'].item(), ) pair_info += tuple([sn[field].item() for field in sn.dtype.names if "feature" in field]) ms.append(pair_info) snote_ids.append(sn['id'].item()) @@ -697,6 +698,7 @@ def to_matched_score(score: ScoreLike, ("velocity", "i4"), ] if include_score_markings: + fields += [("voice", "i4")] fields += [(field, sn.dtype.fields[field][0]) for field in sn.dtype.fields if "feature" in field] return np.array(ms, dtype=fields), snote_ids diff --git a/partitura/musicanalysis/performance_expressions.py b/partitura/musicanalysis/performance_expressions.py new file mode 100644 index 00000000..c51ae6e3 --- /dev/null +++ b/partitura/musicanalysis/performance_expressions.py @@ -0,0 +1,356 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This module implement a series of mid-level descriptors of the performance expressions: asynchrony, dynamics, articulation, phrasing... +Built upon the low-level basis functions from the performance codec. +""" + +# from typing import str +import warnings +import numpy as np +np.set_printoptions(suppress=True) +import matplotlib.pyplot as plt +from scipy import stats +from scipy.optimize import least_squares +import numpy.lib.recfunctions as rfn +from partitura.score import ScoreLike +from partitura.performance import PerformanceLike +from partitura.utils import music +from partitura.musicanalysis.performance_codec import to_matched_score, encode_performance, encode_tempo + + +# ordinal +OLS = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff"] + +def get_performance_expressions(score: ScoreLike, + performance: PerformanceLike, + alignment: list +): + """ + Compute the performance expression attributes + + Parameters + ---------- + score : partitura.score.ScoreLike + Score information, can be a part, score + performance : partitura.performance.PerformanceLike + Performance information, can be a ppart, performance + alignment : list + The score--performance alignment, a list of dictionaries + + Returns + ------- + expressions : dict + + """ + m_score, snote_ids = to_matched_score(score, performance, alignment, include_score_markings=True) + (time_params, unique_onset_idxs) = encode_tempo( + score_onsets=m_score["onset"], + performed_onsets=m_score["p_onset"], + score_durations=m_score["duration"], + performed_durations=m_score["p_duration"], + return_u_onset_idx=True, + tempo_smooth="average" + ) + m_score = rfn.append_fields(m_score, "beat_period", time_params['beat_period'], "f4", usemask=False) + + async_ = async_attributes(unique_onset_idxs, m_score) + dynamics_ = dynamics_attributes(m_score) + articulations_ = articulation_attributes(m_score) + phrasing_ = phrasing_attributes(m_score, + unique_onset_idxs, + # plot=True + ) + return { + "asynchrony": async_, + "dynamics": dynamics_, + "articulations": articulations_, + "phrasing": phrasing_ + } + + +### Asynchrony +def async_attributes(unique_onset_idxs: list, + m_score: list, + v=False): + """ + Compute the asynchrony attributes from the alignment. + - delta: the largest time difference between onsets in this group (-inf, inf) + - pitch_cor: correlation between timing and pitch [-1, 1] + - vel_cor: correlation between timing and velocity [-1, 1] + - voice_std: std of the avg timing of each voice in this group (-inf, inf) + + Parameters + ---------- + unique_onset_idxs : list + a list of arrays with the note indexes that have the same onset + m_score : list + correspondance between score and performance notes, with score markings. + + Returns + ------- + async_ : structured array + structured array (on the onset level) with fields delta, pitch_cor, vel_cor, voice_cor + """ + + async_ = np.zeros(len(unique_onset_idxs), dtype=[( + "delta", "f4"), ("pitch_cor", "f4"), ("vel_cor", "f4"), ("voice_std", "f4")]) + for i, onset_idxs in enumerate(unique_onset_idxs): + + note_group = m_score[onset_idxs] + + onset_times = note_group['p_onset'] + delta = onset_times.max() - onset_times.min() + async_[i]['delta'] = delta + + midi_pitch = note_group['pitch'] + midi_pitch = midi_pitch - midi_pitch.min() # min-scaling + onset_times = onset_times - onset_times.min() + cor = (-1) * np.corrcoef(midi_pitch, onset_times)[0, 1] + # cor=nan if there is only one note in the group + async_[i]['pitch_cor'] = (0 if np.isnan(cor) else cor) + + midi_vel = note_group['velocity'].astype(float) + midi_vel = midi_vel - midi_vel.min() + cor = (-1) * np.corrcoef(midi_vel, onset_times)[0, 1] + async_[i]['vel_cor'] = (0 if np.isnan(cor) else cor) + + voices = np.unique(note_group['voice']) + voices_onsets = [] + for voice in voices: + note_in_voice = note_group[note_group['voice'] == voice] + voices_onsets.append(note_in_voice['p_onset'].mean()) + async_[i]['voice_std'] = np.std(np.array(voices_onsets)) + + return async_ + +def map_fields(note_info, fields): + """ + map the one-hot fields of dynamics and articulation marking into one column with field. + + Args: + note_info (np.array): a row slice from the note_array, without dtype names + fields (list): list of tuples (index, field name) + + Returns: + string: the name of the marking. + """ + + for i, name in fields: + if note_info[i] == 1: + # hack for the return type + return np.array([name.split(".")[-1]], dtype="U256") + return np.array(["N/A"], dtype="U256") + +### Dynamics +def dynamics_attributes(m_score : list, + window : int = 3, + agg : str = "avg"): + """ + Compute the dynamics attributes from the alignment. + + Parameters + ---------- + m_score : structured array + correspondance between score and performance notes, with score markings. + window : int + Length of window to look at the range of dynamic marking coverage, default 3. + agg : string + how to aggregate velocity of notes with the same marking, default avg + + Returns + ------- + dynamics_ : structured array + structured array on the piece level with fields agreement and consistency + """ + dynamics_ = np.zeros(1, dtype=[("dyn_agreement", "f4"), ("dyn_consistency_std", "f4")]) + + fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "loudness" in name] + constant_dyn = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), fields).flatten() + m_score = rfn.rec_append_fields(m_score, "constant_dyn", constant_dyn, "U256") + + # append the marking into m_score based on the time position and windowing + beats_with_constant_dyn = np.unique(m_score[m_score['constant_dyn'] != 'N/A']['onset']) + markings = [m_score[m_score['onset'] == b]['constant_dyn'][0] for b in beats_with_constant_dyn] + velocity = [m_score[m_score['onset'] == b]['velocity'] for b in beats_with_constant_dyn] + velocity_agg = [np.mean(v_group) for v_group in velocity] + + # TODO: changing dynamics + + constant_dynamics = list(zip(markings, velocity_agg)) + + if len(constant_dynamics) < 2: + return dynamics_ + + # agreement: compare each adjacent pair of markings with their expected order, average + tau_total = 0 + for marking1, marking2 in zip(constant_dynamics, constant_dynamics[1:]): + m1, v1 = marking1 + m2, v2 = marking2 + m1_, m2_ = OLS.index(m1), OLS.index(m2) + # preventing correlation returning nan when the values are constant + v2 = v2 + 1e-5 + m2_ = m2_ + 1e-5 + tau, _ = stats.kendalltau([v1, v2], [m1_, m2_]) + assert(tau == tau) # not nan + tau_total += tau + dynamics_['dyn_agreement'] = tau_total / (len(constant_dynamics)-1) + + # consistency: how much fluctuations does each marking have + velocity_std = [np.std(v_group) for v_group in velocity] + dynamics_["dyn_consistency_std"] = np.mean(np.array(velocity_std)) + + return dynamics_ + + +### Articulation +def articulation_attributes(m_score): + """ + Compute the articulation attributes (key overlap ratio) from the alignment. + Key overlap ratio is the ratio between key overlap time (KOT) and IOI, result in a value between (-1, inf) + -1 is the dummy value. + B.Repp: Acoustics, Perception, and Production of Legato Articulation on a Digital Piano + + Parameters + ---------- + m_score : structured array + correspondance between score and performance notes, with score markings. + + Returns + ------- + kor_ : structured array + structured array on the onset level with fields kor, kor_legato, kor_staccato, + kor_repeated + """ + + m_score = rfn.append_fields(m_score, "offset", m_score['onset'] + m_score['duration'], usemask=False) + m_score = rfn.append_fields(m_score, "p_offset", m_score['p_onset'] + m_score['p_duration'], usemask=False) + + kor_ = (-1) * np.ones((len(m_score))) + kor_ = np.array(kor_, dtype=[("kor", "f4"), ("kor_legato", "f4"), ("kor_staccato", "f4"), ("kor_repeated", "f4")]) + + fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "articulation" in name] + articulation = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), fields).flatten() + m_score = rfn.rec_append_fields(m_score, "articulation", articulation, "U256") + + # consider the note transition by each voice + for voice in np.unique(m_score['voice']): + match_voiced = m_score[m_score['voice'] == voice] + for i, note_info in enumerate(match_voiced): + if i >= len(match_voiced) - 1: + break + next_note_info = match_voiced[i + 1] + j = np.where(m_score == note_info)[0].item() # original position + + # KOR for general melodic transitions + if (note_info['offset'] == next_note_info['onset']): + kor_[j]['kor'] = get_kor(note_info, next_note_info) + + # KOR for legato notes - needs refinement + if (note_info['slur_feature.slur_incr'] > 0) or (note_info['slur_feature.slur_decr'] > 0): + kor_[j]['kor_legato'] = get_kor(note_info, next_note_info) + + # KOR for staccato notes + if note_info['articulation'] == 'staccato': + kor_[j]['kor_staccato'] = get_kor(note_info, next_note_info) + + # KOR for repeated notes + if (note_info['pitch'] == next_note_info['pitch']): + kor_[j]['kor_repeated'] = get_kor(note_info, next_note_info) + + return kor_ + +def get_kor(e1, e2): + + kot = e1['p_offset'] - e2['p_onset'] + ioi = e2['p_onset'] - e1['p_onset'] + + kor = kot / ioi + if kor <= -1: + warnings.warn(f"Getting KOR smaller than -1 in {e1['onset']}-{e1['pitch']} and {e2['onset']}-{e2['pitch']}.") + return kor + + +### Phrasing + +def freiberg_kinematic(params, xdata, ydata): + w, q = params + return ydata - (1 + (w ** q - 1) * xdata) ** (1/q) + + +def get_phrase_end(m_score, unique_onset_idxs): + """ + Returns a list of possible phrase endings for analyzing slowing down structure. + (current implementation only takes last 4 beats, need for more advanced segmentation algorithm.) + + Parameters + ---------- + m_score : structured array + correspondance between score and performance notes, with score markings. + unique_onset_idxs : list + a list of arrays with the note indexes that have the same onset + + Returns + ------- + endings : list + list of tuples with (beats, tempo) + """ + beat_first_note = [group[0] for group in unique_onset_idxs] + m_score_beats = m_score[beat_first_note] + + # last 4 beats + final_beat = m_score_beats['onset'][-1] + prase_ending = m_score_beats[(m_score_beats['onset'] >= final_beat - 4)] + xdata, ydata = prase_ending['onset'], 60 / prase_ending['beat_period'] + + endings = [(xdata, ydata)] + return endings + +def phrasing_attributes(m_score, unique_onset_idxs, plot=False): + """ + rubato: + Model the final tempo curve (last 2 measures) using Friberg & Sundberg’s kinematic model: + (https://www.researchgate.net/publication/220723460_Evidence_for_Pianist-specific_Rubato_Style_in_Chopin_Nocturnes) + v(x) = (1 + (w^q − 1) * x)^(1/q), + w: the final tempo (normalized between 0 and 1, assuming normalized ) + q: variation in curvature + + Parameters + ---------- + m_score : structured array + correspondance between score and performance notes, with score markings. + unique_onset_idxs : list + a list of arrays with the note indexes that have the same onset + + Returns + ------- + pharsing_ : structured array + structured array on the (phrase?) level with fields w and q. + """ + + endings = get_phrase_end(m_score, unique_onset_idxs) + phrasing_ = np.zeros(len(endings), dtype=[("rubato_w", "f4"), ("rubato_q", "f4")]) + + for i, ending in enumerate(endings): + xdata, ydata = ending + + # normalize x and y. y: initial tempo as 1 + xdata = (xdata - xdata.min()) * (1 / (xdata.max() - xdata.min())) + ydata = (ydata - 0) * (1 / (ydata.max() - 0)) + + params_init = np.array([0.5, -1]) + res = least_squares(freiberg_kinematic, params_init, args=(xdata, ydata)) + + w, q = res.x + phrasing_[i]['rubato_w'] = w + phrasing_[i]['rubato_q'] = q + + if plot: + plt.scatter(xdata, ydata, marker="+", c="red") + xline = np.linspace(0, 1, 100) + plt.plot(xline, (1 + (w ** q - 1) * xline) ** (1/q)) + plt.ylim(0, 1.2) + plt.title(f"Friberg & Sundberg kinematic rubato curve with w={round(w, 2)} and q={round(q, 2)}") + plt.show() + + return phrasing_ From 3775f268d6a08ed72a32ccf8d543e829e895c637 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Mon, 8 May 2023 14:35:44 +0200 Subject: [PATCH 024/122] performance expressions tmp save --- .../musicanalysis/performance_expressions.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/partitura/musicanalysis/performance_expressions.py b/partitura/musicanalysis/performance_expressions.py index c51ae6e3..fc5f1ded 100644 --- a/partitura/musicanalysis/performance_expressions.py +++ b/partitura/musicanalysis/performance_expressions.py @@ -204,6 +204,19 @@ def dynamics_attributes(m_score : list, ### Articulation + +def get_next_note(i, note_info, match_voiced): + """get the next note in the same voice that's a resonalble transition + """ + + next_position = min(o for o in match_voiced['onset'] if o > note_info['onset']) + next_position_notes = match_voiced[match_voiced['onset'] == next_position] + + # from the notes in the next position, find the one that's closest pitch-wise. + closest_idx = np.abs((next_position_notes['pitch'] - note_info['pitch'])).argmin() + + return next_position_notes[closest_idx] + def articulation_attributes(m_score): """ Compute the articulation attributes (key overlap ratio) from the alignment. @@ -237,9 +250,11 @@ def articulation_attributes(m_score): for voice in np.unique(m_score['voice']): match_voiced = m_score[m_score['voice'] == voice] for i, note_info in enumerate(match_voiced): - if i >= len(match_voiced) - 1: + + if note_info['onset'] == match_voiced['onset'].max(): # last beat break - next_note_info = match_voiced[i + 1] + + next_note_info = get_next_note(i, note_info, match_voiced) j = np.where(m_score == note_info)[0].item() # original position # KOR for general melodic transitions From d9b0e93e5f7754513eac184b2d4c66e90444505f Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Wed, 10 May 2023 14:30:43 +0200 Subject: [PATCH 025/122] dynamics discrete --- partitura/__init__.py | 2 +- partitura/directions.py | 2 +- .../musicanalysis/performance_expressions.py | 83 ++++++++++++------- tests/test_performance_expressions.py | 0 4 files changed, 53 insertions(+), 34 deletions(-) create mode 100644 tests/test_performance_expressions.py diff --git a/partitura/__init__.py b/partitura/__init__.py index 074b8726..f2080b2b 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -27,7 +27,7 @@ from .musicanalysis import make_note_features, compute_note_array, full_note_array # define a version variable -__version__ = pkg_resources.get_distribution("partitura").version +# __version__ = pkg_resources.get_distribution("partitura").version #: An example MusicXML file for didactic purposes EXAMPLE_MUSICXML = pkg_resources.resource_filename( diff --git a/partitura/directions.py b/partitura/directions.py index 6c2cd289..80043d13 100644 --- a/partitura/directions.py +++ b/partitura/directions.py @@ -98,7 +98,7 @@ def unabbreviate(s): "rubato", ] DEC_TEMPO_ADJ = [ - r"/(ritenuto|ritenente|riten\.?)/", + r"/(ritenuto|(ritenente|riten)\.?)/", r"/(ritardando|(ritard|rit)\.?)/", r"/(rallentando|(rallent|rall)\.?)/", ] diff --git a/partitura/musicanalysis/performance_expressions.py b/partitura/musicanalysis/performance_expressions.py index fc5f1ded..31115ff3 100644 --- a/partitura/musicanalysis/performance_expressions.py +++ b/partitura/musicanalysis/performance_expressions.py @@ -19,6 +19,35 @@ from partitura.musicanalysis.performance_codec import to_matched_score, encode_performance, encode_tempo +def map_fields(note_info, fields): + """ + map the one-hot fields of dynamics and articulation marking into one column with field. + + Args: + note_info (np.array): a row slice from the note_array, without dtype names + fields (list): list of tuples (index, field name) + + Returns: + string: the name of the marking. + """ + + for i, name in fields: + if note_info[i] == 1: + # hack for the return type + return np.array([name.split(".")[-1]], dtype="U256") + return np.array(["N/A"], dtype="U256") + + +def process_discrete_dynamics(constant_dyn): + """reverse the continuous dynamics ramp into discrete marks on the first beat. Rest of the events are filled with N/A""" + constant_dyn_shift = np.append(["N/A"], constant_dyn[:-1]) + positions = np.where(constant_dyn != constant_dyn_shift)[0] + + constant_dyn_ = np.full(len(constant_dyn), "N/A") + constant_dyn_[positions] = constant_dyn[positions] + + return constant_dyn_ + # ordinal OLS = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff"] @@ -54,6 +83,18 @@ def get_performance_expressions(score: ScoreLike, ) m_score = rfn.append_fields(m_score, "beat_period", time_params['beat_period'], "f4", usemask=False) + dyn_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "loudness" in name] + constant_dyn = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), dyn_fields).flatten() + + # process the dynamcis value into discrete markings on the first beat instead of a ramp. + constant_dyn = process_discrete_dynamics(constant_dyn) + + art_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "articulation" in name] + articulation = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), art_fields).flatten() + + m_score = rfn.rec_append_fields(m_score, "constant_dyn", constant_dyn, "U256") + m_score = rfn.rec_append_fields(m_score, "articulation", articulation, "U256") + async_ = async_attributes(unique_onset_idxs, m_score) dynamics_ = dynamics_attributes(m_score) articulations_ = articulation_attributes(m_score) @@ -62,6 +103,7 @@ def get_performance_expressions(score: ScoreLike, # plot=True ) return { + "m_score": m_score, "asynchrony": async_, "dynamics": dynamics_, "articulations": articulations_, @@ -124,23 +166,6 @@ def async_attributes(unique_onset_idxs: list, return async_ -def map_fields(note_info, fields): - """ - map the one-hot fields of dynamics and articulation marking into one column with field. - - Args: - note_info (np.array): a row slice from the note_array, without dtype names - fields (list): list of tuples (index, field name) - - Returns: - string: the name of the marking. - """ - - for i, name in fields: - if note_info[i] == 1: - # hack for the return type - return np.array([name.split(".")[-1]], dtype="U256") - return np.array(["N/A"], dtype="U256") ### Dynamics def dynamics_attributes(m_score : list, @@ -160,14 +185,11 @@ def dynamics_attributes(m_score : list, Returns ------- - dynamics_ : structured array - structured array on the piece level with fields agreement and consistency + dynamics_ : dict + dictionary with fields agreement and consistency """ - dynamics_ = np.zeros(1, dtype=[("dyn_agreement", "f4"), ("dyn_consistency_std", "f4")]) - - fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "loudness" in name] - constant_dyn = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), fields).flatten() - m_score = rfn.rec_append_fields(m_score, "constant_dyn", constant_dyn, "U256") + # dynamics_ = np.zeros(1, dtype=[("agreement", "f4"), ("consistency_std", "f4")]) + dynamics_ = dict() # append the marking into m_score based on the time position and windowing beats_with_constant_dyn = np.unique(m_score[m_score['constant_dyn'] != 'N/A']['onset']) @@ -183,7 +205,7 @@ def dynamics_attributes(m_score : list, return dynamics_ # agreement: compare each adjacent pair of markings with their expected order, average - tau_total = 0 + marking_aggrements = [] for marking1, marking2 in zip(constant_dynamics, constant_dynamics[1:]): m1, v1 = marking1 m2, v2 = marking2 @@ -193,12 +215,13 @@ def dynamics_attributes(m_score : list, m2_ = m2_ + 1e-5 tau, _ = stats.kendalltau([v1, v2], [m1_, m2_]) assert(tau == tau) # not nan - tau_total += tau - dynamics_['dyn_agreement'] = tau_total / (len(constant_dynamics)-1) + marking_aggrements.append((f"{m1}-{m2}", tau)) + + dynamics_['agreement'] = marking_aggrements # consistency: how much fluctuations does each marking have velocity_std = [np.std(v_group) for v_group in velocity] - dynamics_["dyn_consistency_std"] = np.mean(np.array(velocity_std)) + dynamics_["consistency_std"] = np.mean(np.array(velocity_std)) return dynamics_ @@ -242,10 +265,6 @@ def articulation_attributes(m_score): kor_ = (-1) * np.ones((len(m_score))) kor_ = np.array(kor_, dtype=[("kor", "f4"), ("kor_legato", "f4"), ("kor_staccato", "f4"), ("kor_repeated", "f4")]) - fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "articulation" in name] - articulation = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), fields).flatten() - m_score = rfn.rec_append_fields(m_score, "articulation", articulation, "U256") - # consider the note transition by each voice for voice in np.unique(m_score['voice']): match_voiced = m_score[m_score['voice'] == voice] diff --git a/tests/test_performance_expressions.py b/tests/test_performance_expressions.py new file mode 100644 index 00000000..e69de29b From 7082a3b200730f803c6aba52b061d31b460b590e Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Thu, 11 May 2023 13:56:04 +0200 Subject: [PATCH 026/122] ramp_cor for dynamics --- .../musicanalysis/performance_expressions.py | 69 ++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/partitura/musicanalysis/performance_expressions.py b/partitura/musicanalysis/performance_expressions.py index 31115ff3..615ea69b 100644 --- a/partitura/musicanalysis/performance_expressions.py +++ b/partitura/musicanalysis/performance_expressions.py @@ -48,6 +48,27 @@ def process_discrete_dynamics(constant_dyn): return constant_dyn_ + +def parse_changing_ramp(unique_onset_idxs, m_score): + """parse the cresceando / decresceando ramp for the actively changing subsequences. + Return a list of (start, end) of the changing subsequences.""" + + unique_m_score = m_score[[idx[0] for idx in unique_onset_idxs]] + + increase = unique_m_score['loudness_direction_feature.loudness_incr'] + decrease = unique_m_score['loudness_direction_feature.loudness_decr'] + + onset_boundaries = [] + # finding the increase & decrease boundaries + for ramp in [increase, decrease]: + ramp_diff = np.append(ramp[1:], ramp[-1]) - ramp + has_ramp_diff = ramp_diff != 0 + ramp_boundary = np.append(has_ramp_diff[0], has_ramp_diff[:-1]) ^ has_ramp_diff + onset_boundary = unique_m_score[ramp_boundary]['onset'] + onset_boundaries.append([(onset_boundary[i], onset_boundary[i+1]) for i in range(0, len(onset_boundary), 2)]) + + return tuple(onset_boundaries) + # ordinal OLS = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff"] @@ -96,7 +117,7 @@ def get_performance_expressions(score: ScoreLike, m_score = rfn.rec_append_fields(m_score, "articulation", articulation, "U256") async_ = async_attributes(unique_onset_idxs, m_score) - dynamics_ = dynamics_attributes(m_score) + dynamics_ = dynamics_attributes(unique_onset_idxs, m_score) articulations_ = articulation_attributes(m_score) phrasing_ = phrasing_attributes(m_score, unique_onset_idxs, @@ -168,7 +189,8 @@ def async_attributes(unique_onset_idxs: list, ### Dynamics -def dynamics_attributes(m_score : list, +def dynamics_attributes(unique_onset_idxs: list, + m_score : list, window : int = 3, agg : str = "avg"): """ @@ -187,6 +209,10 @@ def dynamics_attributes(m_score : list, ------- dynamics_ : dict dictionary with fields agreement and consistency + agreement: + consistency: + ramp_cor: + tempo_cor: """ # dynamics_ = np.zeros(1, dtype=[("agreement", "f4"), ("consistency_std", "f4")]) dynamics_ = dict() @@ -194,11 +220,12 @@ def dynamics_attributes(m_score : list, # append the marking into m_score based on the time position and windowing beats_with_constant_dyn = np.unique(m_score[m_score['constant_dyn'] != 'N/A']['onset']) markings = [m_score[m_score['onset'] == b]['constant_dyn'][0] for b in beats_with_constant_dyn] + + # TODO: windowing + velocity = [m_score[m_score['onset'] == b]['velocity'] for b in beats_with_constant_dyn] velocity_agg = [np.mean(v_group) for v_group in velocity] - # TODO: changing dynamics - constant_dynamics = list(zip(markings, velocity_agg)) if len(constant_dynamics) < 2: @@ -216,12 +243,38 @@ def dynamics_attributes(m_score : list, tau, _ = stats.kendalltau([v1, v2], [m1_, m2_]) assert(tau == tau) # not nan marking_aggrements.append((f"{m1}-{m2}", tau)) - - dynamics_['agreement'] = marking_aggrements # consistency: how much fluctuations does each marking have - velocity_std = [np.std(v_group) for v_group in velocity] - dynamics_["consistency_std"] = np.mean(np.array(velocity_std)) + markings = np.array(markings) + velocity_agg = np.array(velocity_agg, dtype=object) + marking_consistency = [] + for marking in np.unique(markings): + marking_std = np.std(np.hstack(velocity_agg[markings == marking])) + marking_consistency.append((f"{marking}", marking_std)) + + dynamics_['agreement'] = marking_aggrements + dynamics_["consistency_std"] = marking_consistency + + # changing dynamics + increase_ob, decrease_ob = parse_changing_ramp(unique_onset_idxs, m_score) + ramp_cor_incr, ramp_cor_decr = [], [] + for start, end in increase_ob: + score_dynamics, performed_dyanmics = [], [] + for onset in m_score[(m_score['onset'] >= start) & (m_score['onset'] <= end)]['onset']: + score_dynamics.append(m_score[m_score['onset'] == onset][0]['loudness_direction_feature.loudness_incr']) + performed_dyanmics.append(m_score[m_score['onset'] == onset]['velocity'].mean()) + ramp_cor_incr.append(stats.pearsonr(score_dynamics, performed_dyanmics)[0]) + + for start, end in decrease_ob: + score_dynamics, performed_dyanmics = [], [] + for onset in m_score[(m_score['onset'] >= start) & (m_score['onset'] <= end)]['onset']: + score_dynamics.append((-1) * m_score[m_score['onset'] == onset][0]['loudness_direction_feature.loudness_decr']) + performed_dyanmics.append(m_score[m_score['onset'] == onset]['velocity'].mean()) + ramp_cor_decr.append(stats.pearsonr(score_dynamics, performed_dyanmics)[0]) + + + dynamics_['ramp_cor_incr'] = np.array(ramp_cor_incr) + dynamics_['ramp_cor_decr'] = np.array(ramp_cor_decr) return dynamics_ From 1e3f9b163d56110854e17a1b78eb28a96728c2e1 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Fri, 12 May 2023 17:50:55 +0200 Subject: [PATCH 027/122] dynamics features standardized --- partitura/musicanalysis/__init__.py | 2 +- ...expressions.py => performance_features.py} | 72 +++++++++---------- 2 files changed, 33 insertions(+), 41 deletions(-) rename partitura/musicanalysis/{performance_expressions.py => performance_features.py} (87%) diff --git a/partitura/musicanalysis/__init__.py b/partitura/musicanalysis/__init__.py index 84d3d714..5d3741af 100644 --- a/partitura/musicanalysis/__init__.py +++ b/partitura/musicanalysis/__init__.py @@ -21,7 +21,7 @@ make_rest_features, ) from .performance_codec import encode_performance, decode_performance -from .performance_expressions import get_performance_expressions +from .performance_features import get_performance_expressions __all__ = [ diff --git a/partitura/musicanalysis/performance_expressions.py b/partitura/musicanalysis/performance_features.py similarity index 87% rename from partitura/musicanalysis/performance_expressions.py rename to partitura/musicanalysis/performance_features.py index 615ea69b..0abbe87f 100644 --- a/partitura/musicanalysis/performance_expressions.py +++ b/partitura/musicanalysis/performance_features.py @@ -67,7 +67,7 @@ def parse_changing_ramp(unique_onset_idxs, m_score): onset_boundary = unique_m_score[ramp_boundary]['onset'] onset_boundaries.append([(onset_boundary[i], onset_boundary[i+1]) for i in range(0, len(onset_boundary), 2)]) - return tuple(onset_boundaries) + return tuple(onset_boundaries), unique_m_score # ordinal OLS = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff"] @@ -153,7 +153,7 @@ def async_attributes(unique_onset_idxs: list, Returns ------- async_ : structured array - structured array (on the onset level) with fields delta, pitch_cor, vel_cor, voice_cor + structured array (on the beat level) with fields delta, pitch_cor, vel_cor, voice_cor """ async_ = np.zeros(len(unique_onset_idxs), dtype=[( @@ -207,22 +207,20 @@ def dynamics_attributes(unique_onset_idxs: list, Returns ------- - dynamics_ : dict - dictionary with fields agreement and consistency + dynamics_ : structured array (broadcasted to the note level) with the following fields agreement: consistency: ramp_cor: tempo_cor: """ - # dynamics_ = np.zeros(1, dtype=[("agreement", "f4"), ("consistency_std", "f4")]) - dynamics_ = dict() + # dynamics_ = dict() + dynamics_ = np.zeros(len(m_score), dtype=[( + "agreement", "f4"), ("consistency_std", "f4"), ("ramp_cor", "f4"), ("tempo_cor", "f4")]) # append the marking into m_score based on the time position and windowing beats_with_constant_dyn = np.unique(m_score[m_score['constant_dyn'] != 'N/A']['onset']) markings = [m_score[m_score['onset'] == b]['constant_dyn'][0] for b in beats_with_constant_dyn] - # TODO: windowing - velocity = [m_score[m_score['onset'] == b]['velocity'] for b in beats_with_constant_dyn] velocity_agg = [np.mean(v_group) for v_group in velocity] @@ -232,49 +230,44 @@ def dynamics_attributes(unique_onset_idxs: list, return dynamics_ # agreement: compare each adjacent pair of markings with their expected order, average - marking_aggrements = [] - for marking1, marking2 in zip(constant_dynamics, constant_dynamics[1:]): - m1, v1 = marking1 - m2, v2 = marking2 + marking_agreements = [] + for marking1, marking2, beat in zip(constant_dynamics, constant_dynamics[1:], beats_with_constant_dyn[1:]): + (m1, v1), (m2, v2) = marking1, marking2 m1_, m2_ = OLS.index(m1), OLS.index(m2) # preventing correlation returning nan when the values are constant v2 = v2 + 1e-5 m2_ = m2_ + 1e-5 tau, _ = stats.kendalltau([v1, v2], [m1_, m2_]) assert(tau == tau) # not nan - marking_aggrements.append((f"{m1}-{m2}", tau)) + marking_agreements.append((f"{m1}-{m2}", tau)) + dynamics_['agreement'][m_score['onset'] == beat] = tau # consistency: how much fluctuations does each marking have markings = np.array(markings) velocity_agg = np.array(velocity_agg, dtype=object) marking_consistency = [] - for marking in np.unique(markings): + for marking, beat in zip(np.unique(markings), beats_with_constant_dyn): marking_std = np.std(np.hstack(velocity_agg[markings == marking])) marking_consistency.append((f"{marking}", marking_std)) - - dynamics_['agreement'] = marking_aggrements - dynamics_["consistency_std"] = marking_consistency - - # changing dynamics - increase_ob, decrease_ob = parse_changing_ramp(unique_onset_idxs, m_score) - ramp_cor_incr, ramp_cor_decr = [], [] - for start, end in increase_ob: - score_dynamics, performed_dyanmics = [], [] - for onset in m_score[(m_score['onset'] >= start) & (m_score['onset'] <= end)]['onset']: - score_dynamics.append(m_score[m_score['onset'] == onset][0]['loudness_direction_feature.loudness_incr']) - performed_dyanmics.append(m_score[m_score['onset'] == onset]['velocity'].mean()) - ramp_cor_incr.append(stats.pearsonr(score_dynamics, performed_dyanmics)[0]) - - for start, end in decrease_ob: - score_dynamics, performed_dyanmics = [], [] - for onset in m_score[(m_score['onset'] >= start) & (m_score['onset'] <= end)]['onset']: - score_dynamics.append((-1) * m_score[m_score['onset'] == onset][0]['loudness_direction_feature.loudness_decr']) - performed_dyanmics.append(m_score[m_score['onset'] == onset]['velocity'].mean()) - ramp_cor_decr.append(stats.pearsonr(score_dynamics, performed_dyanmics)[0]) - - - dynamics_['ramp_cor_incr'] = np.array(ramp_cor_incr) - dynamics_['ramp_cor_decr'] = np.array(ramp_cor_decr) + dynamics_['consistency_std'][m_score['onset'] == beat] = marking_std + + + # changing dynamics - correlation with each incr and decr ramp + (increase_ob, decrease_ob), unique_m_score = parse_changing_ramp(unique_onset_idxs, m_score) + for onset_boundaries, feat_name in zip([increase_ob, decrease_ob], + ['loudness_direction_feature.loudness_incr', 'loudness_direction_feature.loudness_decr']): + for start, end in onset_boundaries: + score_dynamics, performed_dyanmics, performed_bp = [], [], [] + for onset in unique_m_score[(unique_m_score['onset'] >= start) & (unique_m_score['onset'] <= end)]['onset']: + score_dynamics.append(unique_m_score[unique_m_score['onset'] == onset][0][feat_name]) + performed_dyanmics.append(unique_m_score[unique_m_score['onset'] == onset]['velocity'].mean()) + performed_bp.append(unique_m_score[unique_m_score['onset'] == onset]['beat_period'].item()) + cor = stats.pearsonr(score_dynamics, performed_dyanmics)[0] + if "decr" in feat_name: + cor *= -1 + ramp_mask = (m_score['onset'] >= start) & (m_score['onset'] <= end) + dynamics_['ramp_cor'][ramp_mask] = cor + dynamics_['tempo_cor'][ramp_mask] = (-1) * stats.pearsonr(performed_bp, performed_dyanmics)[0] return dynamics_ @@ -308,8 +301,7 @@ def articulation_attributes(m_score): Returns ------- kor_ : structured array - structured array on the onset level with fields kor, kor_legato, kor_staccato, - kor_repeated + structured array on the onset level with fields kor, kor_legato, kor_staccato, kor_repeated """ m_score = rfn.append_fields(m_score, "offset", m_score['onset'] + m_score['duration'], usemask=False) From 941a1f2b88496f2c0e9a1b10b0bc268b5a7beaef Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Mon, 15 May 2023 14:51:34 +0200 Subject: [PATCH 028/122] articulation - cap max value, case with no transition, mask for legato staccato repeated --- .../musicanalysis/performance_features.py | 74 +++++++++++-------- 1 file changed, 43 insertions(+), 31 deletions(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 0abbe87f..63068fa1 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -118,7 +118,7 @@ def get_performance_expressions(score: ScoreLike, async_ = async_attributes(unique_onset_idxs, m_score) dynamics_ = dynamics_attributes(unique_onset_idxs, m_score) - articulations_ = articulation_attributes(m_score) + articulations_ = articulation_attributes(m_score, return_mask=True) phrasing_ = phrasing_attributes(m_score, unique_onset_idxs, # plot=True @@ -208,12 +208,11 @@ def dynamics_attributes(unique_onset_idxs: list, Returns ------- dynamics_ : structured array (broadcasted to the note level) with the following fields - agreement: - consistency: - ramp_cor: - tempo_cor: + agreement [-1, 1]: for each pair of dynamics, whether it agree with the OLS. Default 0 + consistency_std []: Default 0 + ramp_cor [-1, 1]: Default 0 + tempo_cor [-1, 1]: Default 0 """ - # dynamics_ = dict() dynamics_ = np.zeros(len(m_score), dtype=[( "agreement", "f4"), ("consistency_std", "f4"), ("ramp_cor", "f4"), ("tempo_cor", "f4")]) @@ -274,11 +273,18 @@ def dynamics_attributes(unique_onset_idxs: list, ### Articulation -def get_next_note(i, note_info, match_voiced): +def get_next_note(note_info, match_voiced): """get the next note in the same voice that's a resonalble transition + note_info: the row of current note + match_voiced: all notes in the same voice """ next_position = min(o for o in match_voiced['onset'] if o > note_info['onset']) + + # if the next note is not immediate successor of the previous one... + if next_position != note_info['onset'] + note_info['duration']: + return None + next_position_notes = match_voiced[match_voiced['onset'] == next_position] # from the notes in the next position, find the one that's closest pitch-wise. @@ -286,58 +292,63 @@ def get_next_note(i, note_info, match_voiced): return next_position_notes[closest_idx] -def articulation_attributes(m_score): +def articulation_attributes(m_score, return_mask=False): """ Compute the articulation attributes (key overlap ratio) from the alignment. Key overlap ratio is the ratio between key overlap time (KOT) and IOI, result in a value between (-1, inf) - -1 is the dummy value. + -1 is the dummy value. For normalization purposes we empirically cap the maximum to 5. B.Repp: Acoustics, Perception, and Production of Legato Articulation on a Digital Piano Parameters ---------- m_score : structured array correspondance between score and performance notes, with score markings. + return_mask : bool + if true, return a boolean mask of legato notes, staccato notes and repeated notes Returns ------- - kor_ : structured array - structured array on the onset level with fields kor, kor_legato, kor_staccato, kor_repeated + kor_ : structured array (1, n_notes) + structured array on the note level with fields kor (-1, 5] """ m_score = rfn.append_fields(m_score, "offset", m_score['onset'] + m_score['duration'], usemask=False) m_score = rfn.append_fields(m_score, "p_offset", m_score['p_onset'] + m_score['p_duration'], usemask=False) - kor_ = (-1) * np.ones((len(m_score))) - kor_ = np.array(kor_, dtype=[("kor", "f4"), ("kor_legato", "f4"), ("kor_staccato", "f4"), ("kor_repeated", "f4")]) + kor_ = np.full(len(m_score), -1, dtype=[("kor", "f4")]) + if return_mask: + mask = np.full(len(m_score), False, dtype=[("legato", "?"), ("staccato", "?"), ("repeated", "?")]) # consider the note transition by each voice for voice in np.unique(m_score['voice']): match_voiced = m_score[m_score['voice'] == voice] - for i, note_info in enumerate(match_voiced): + for _, note_info in enumerate(match_voiced): if note_info['onset'] == match_voiced['onset'].max(): # last beat break + next_note_info = get_next_note(note_info, match_voiced) # find most plausible transition - next_note_info = get_next_note(i, note_info, match_voiced) - j = np.where(m_score == note_info)[0].item() # original position - - # KOR for general melodic transitions - if (note_info['offset'] == next_note_info['onset']): - kor_[j]['kor'] = get_kor(note_info, next_note_info) + if next_note_info: # in some cases no meaningful transition + j = np.where(m_score == note_info)[0].item() # original position - # KOR for legato notes - needs refinement - if (note_info['slur_feature.slur_incr'] > 0) or (note_info['slur_feature.slur_decr'] > 0): - kor_[j]['kor_legato'] = get_kor(note_info, next_note_info) + if (note_info['offset'] == next_note_info['onset']): + kor_[j]['kor'] = get_kor(note_info, next_note_info) - # KOR for staccato notes - if note_info['articulation'] == 'staccato': - kor_[j]['kor_staccato'] = get_kor(note_info, next_note_info) + if return_mask: # return the + if (note_info['slur_feature.slur_incr'] > 0) or (note_info['slur_feature.slur_decr'] > 0): + mask[j]['legato'] = True - # KOR for repeated notes - if (note_info['pitch'] == next_note_info['pitch']): - kor_[j]['kor_repeated'] = get_kor(note_info, next_note_info) + if note_info['articulation'] == 'staccato': + mask[j]['staccato'] = True - return kor_ + # KOR for repeated notes + if (note_info['pitch'] == next_note_info['pitch']): + mask[j]['repeated'] = True + + if return_mask: + return kor_, mask + else: + return kor_ def get_kor(e1, e2): @@ -347,7 +358,8 @@ def get_kor(e1, e2): kor = kot / ioi if kor <= -1: warnings.warn(f"Getting KOR smaller than -1 in {e1['onset']}-{e1['pitch']} and {e2['onset']}-{e2['pitch']}.") - return kor + + return min(kor, 5) ### Phrasing From ad1e817e3334072038a6609a23df57f99783c0e3 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Tue, 16 May 2023 10:42:19 +0200 Subject: [PATCH 029/122] basic features, size aligned, value range documented, default value set (no NaN) --- partitura/musicanalysis/__init__.py | 4 +- .../musicanalysis/performance_features.py | 269 ++++++++++++------ 2 files changed, 189 insertions(+), 84 deletions(-) diff --git a/partitura/musicanalysis/__init__.py b/partitura/musicanalysis/__init__.py index 5d3741af..7b424b01 100644 --- a/partitura/musicanalysis/__init__.py +++ b/partitura/musicanalysis/__init__.py @@ -21,7 +21,7 @@ make_rest_features, ) from .performance_codec import encode_performance, decode_performance -from .performance_features import get_performance_expressions +from .performance_features import compute_performance_features __all__ = [ @@ -37,5 +37,5 @@ "decode_performance", "compute_note_array", "full_note_array", - "get_performance_expressions" + "compute_performance_features" ] diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 63068fa1..6bc5b015 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -5,7 +5,9 @@ Built upon the low-level basis functions from the performance codec. """ -# from typing import str +import sys +import types +from typing import Union, List import warnings import numpy as np np.set_printoptions(suppress=True) @@ -15,8 +17,170 @@ import numpy.lib.recfunctions as rfn from partitura.score import ScoreLike from partitura.performance import PerformanceLike -from partitura.utils import music -from partitura.musicanalysis.performance_codec import to_matched_score, encode_performance, encode_tempo +from partitura.musicanalysis.performance_codec import to_matched_score, onsetwise_to_notewise, encode_tempo + + +__all__ = [ + "compute_performance_features", +] + +# ordinal +OLS = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff"] + + +class InvalidPerformanceFeatureException(Exception): + pass + +def compute_performance_features(score: ScoreLike, + performance: PerformanceLike, + alignment: list, + feature_functions: Union[List, str] +): + """ + Compute the performance features. This function is defined in the same + style of note_features.make_note_features + + Parameters + ---------- + score : partitura.score.ScoreLike + Score information, can be a part, score + performance : partitura.performance.PerformanceLike + Performance information, can be a ppart, performance + alignment : list + The score--performance alignment, a list of dictionaries + feature_functions : list or str + A list of performance feature functions. Elements of the list can be either + the functions themselves or the names of a feature function as + strings (or a mix). + currently implemented: + asynchrony_feature, articulation_feature, dynamics_feature + + Returns + ------- + performance_features : structured array + """ + m_score, unique_onset_idxs = compute_matched_score(score, performance, alignment) + + acc = [] + if isinstance(feature_functions, str) and feature_functions == "all": + feature_functions = list_note_feats_functions() + elif not isinstance(feature_functions, list): + raise TypeError( + "feature_functions variable {} needs to be list or all".format( + feature_functions + ) + ) + + for bf in feature_functions: + if isinstance(bf, str): + # get function by name from module + func = getattr(sys.modules[__name__], bf) + elif isinstance(bf, types.FunctionType): + func = bf + else: + warnings.warn("Ignoring unknown performance feature function {}".format(bf)) + features = func(m_score, unique_onset_idxs) + # check if the size and number of the feature function are correct + if features.size != 0: + n_notes = len(m_score) + if len(features) != n_notes: + msg = ( + "length of feature {} does not equal " + "number of notes {}".format(len(features), n_notes) + ) + raise InvalidPerformanceFeatureException(msg) + + features_ = rfn.structured_to_unstructured(features) + if np.any(np.logical_or(np.isnan(features_), np.isinf(features_))): + problematic = np.unique( + np.where(np.logical_or(np.isnan(features_), np.isinf(features_)))[1] + ) + msg = "NaNs or Infs found in the following feature: {} ".format( + ", ".join(np.array(features.dtype)[problematic]) + ) + raise InvalidPerformanceFeatureException(msg) + + # prefix feature names by function name + feature_names = ["{}.{}".format(func.__name__, n) for n in features.dtype.names] + features = rfn.rename_fields(features, dict(zip(features.dtype.names, feature_names))) + + acc.append(features) + + performance_features = rfn.merge_arrays(acc, flatten=True, usemask=False) + return performance_features + + +def compute_matched_score(score: ScoreLike, + performance: PerformanceLike, + alignment: list,): + """ + Compute the matched score and add the score features + + Parameters + ---------- + score : partitura.score.ScoreLike + Score information, can be a part, score + performance : partitura.performance.PerformanceLike + Performance information, can be a ppart, performance + alignment : list + The score--performance alignment, a list of dictionaries + + Returns + ------- + m_score : np strutured array + unique_onset_idxs : list + """ + + m_score, _ = to_matched_score(score, performance, alignment, include_score_markings=True) + (time_params, unique_onset_idxs) = encode_tempo( + score_onsets=m_score["onset"], + performed_onsets=m_score["p_onset"], + score_durations=m_score["duration"], + performed_durations=m_score["p_duration"], + return_u_onset_idx=True, + tempo_smooth="average" + ) + m_score = rfn.append_fields(m_score, "beat_period", time_params['beat_period'], "f4", usemask=False) + + dyn_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "loudness" in name] + constant_dyn = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), dyn_fields).flatten() + + # process the dynamcis value into discrete markings on the first beat instead of a ramp. + constant_dyn = process_discrete_dynamics(constant_dyn) + + art_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "articulation" in name] + articulation = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), art_fields).flatten() + + m_score = rfn.rec_append_fields(m_score, "constant_dyn", constant_dyn, "U256") + m_score = rfn.rec_append_fields(m_score, "articulation", articulation, "U256") + + return m_score, unique_onset_idxs + + +def list_note_feats_functions(): + """Return a list of all feature function names defined in this module. + + The feature function names listed here can be specified by name in + the `make_feature` function. For example: + + >>> feature, names = make_note_feats(part, ['metrical_feature', 'articulation_feature']) + + Returns + ------- + list + A list of strings + + """ + module = sys.modules[__name__] + bfs = [] + exclude = {"make_feature"} + for name in dir(module): + if name in exclude: + continue + member = getattr(sys.modules[__name__], name) + if isinstance(member, types.FunctionType) and name.endswith("_feature"): + bfs.append(name) + return bfs def map_fields(note_info, fields): @@ -69,79 +233,13 @@ def parse_changing_ramp(unique_onset_idxs, m_score): return tuple(onset_boundaries), unique_m_score -# ordinal -OLS = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff"] - -def get_performance_expressions(score: ScoreLike, - performance: PerformanceLike, - alignment: list -): - """ - Compute the performance expression attributes - - Parameters - ---------- - score : partitura.score.ScoreLike - Score information, can be a part, score - performance : partitura.performance.PerformanceLike - Performance information, can be a ppart, performance - alignment : list - The score--performance alignment, a list of dictionaries - - Returns - ------- - expressions : dict - - """ - m_score, snote_ids = to_matched_score(score, performance, alignment, include_score_markings=True) - (time_params, unique_onset_idxs) = encode_tempo( - score_onsets=m_score["onset"], - performed_onsets=m_score["p_onset"], - score_durations=m_score["duration"], - performed_durations=m_score["p_duration"], - return_u_onset_idx=True, - tempo_smooth="average" - ) - m_score = rfn.append_fields(m_score, "beat_period", time_params['beat_period'], "f4", usemask=False) - - dyn_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "loudness" in name] - constant_dyn = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), dyn_fields).flatten() - - # process the dynamcis value into discrete markings on the first beat instead of a ramp. - constant_dyn = process_discrete_dynamics(constant_dyn) - - art_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "articulation" in name] - articulation = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), art_fields).flatten() - - m_score = rfn.rec_append_fields(m_score, "constant_dyn", constant_dyn, "U256") - m_score = rfn.rec_append_fields(m_score, "articulation", articulation, "U256") - - async_ = async_attributes(unique_onset_idxs, m_score) - dynamics_ = dynamics_attributes(unique_onset_idxs, m_score) - articulations_ = articulation_attributes(m_score, return_mask=True) - phrasing_ = phrasing_attributes(m_score, - unique_onset_idxs, - # plot=True - ) - return { - "m_score": m_score, - "asynchrony": async_, - "dynamics": dynamics_, - "articulations": articulations_, - "phrasing": phrasing_ - } - ### Asynchrony -def async_attributes(unique_onset_idxs: list, - m_score: list, +def asynchrnoy_feature(m_score: list, + unique_onset_idxs: list, v=False): """ Compute the asynchrony attributes from the alignment. - - delta: the largest time difference between onsets in this group (-inf, inf) - - pitch_cor: correlation between timing and pitch [-1, 1] - - vel_cor: correlation between timing and velocity [-1, 1] - - voice_std: std of the avg timing of each voice in this group (-inf, inf) Parameters ---------- @@ -153,7 +251,11 @@ def async_attributes(unique_onset_idxs: list, Returns ------- async_ : structured array - structured array (on the beat level) with fields delta, pitch_cor, vel_cor, voice_cor + structured array (broadcasted to the note level) with the following fields + delta: the largest time difference (in seconds) between onsets in this group [0, 1] + pitch_cor: correlation between timing and pitch [-1, 1] + vel_cor: correlation between timing and velocity [-1, 1] + voice_std: std of the avg timing (in seconds) of each voice in this group [0, 1] """ async_ = np.zeros(len(unique_onset_idxs), dtype=[( @@ -163,7 +265,7 @@ def async_attributes(unique_onset_idxs: list, note_group = m_score[onset_idxs] onset_times = note_group['p_onset'] - delta = onset_times.max() - onset_times.min() + delta = min(onset_times.max() - onset_times.min(), 1) async_[i]['delta'] = delta midi_pitch = note_group['pitch'] @@ -183,14 +285,14 @@ def async_attributes(unique_onset_idxs: list, for voice in voices: note_in_voice = note_group[note_group['voice'] == voice] voices_onsets.append(note_in_voice['p_onset'].mean()) - async_[i]['voice_std'] = np.std(np.array(voices_onsets)) - - return async_ + async_[i]['voice_std'] = min(np.std(np.array(voices_onsets)), 1) + + return onsetwise_to_notewise(async_, unique_onset_idxs) ### Dynamics -def dynamics_attributes(unique_onset_idxs: list, - m_score : list, +def dynamics_feature(m_score : list, + unique_onset_idxs: list, window : int = 3, agg : str = "avg"): """ @@ -209,9 +311,9 @@ def dynamics_attributes(unique_onset_idxs: list, ------- dynamics_ : structured array (broadcasted to the note level) with the following fields agreement [-1, 1]: for each pair of dynamics, whether it agree with the OLS. Default 0 - consistency_std []: Default 0 - ramp_cor [-1, 1]: Default 0 - tempo_cor [-1, 1]: Default 0 + consistency_std [0, 127]: Std of the same marking thoughout the piece. Default 0 + ramp_cor [-1, 1]: The correlation between each dynamics ramp and performed dynamics. Default 0 + tempo_cor [-1, 1]: The correlation between performed dynamics and tempo change. Default 0 """ dynamics_ = np.zeros(len(m_score), dtype=[( "agreement", "f4"), ("consistency_std", "f4"), ("ramp_cor", "f4"), ("tempo_cor", "f4")]) @@ -292,7 +394,9 @@ def get_next_note(note_info, match_voiced): return next_position_notes[closest_idx] -def articulation_attributes(m_score, return_mask=False): +def articulation_feature(m_score : list, + unique_onset_idxs: list, + return_mask=False): """ Compute the articulation attributes (key overlap ratio) from the alignment. Key overlap ratio is the ratio between key overlap time (KOT) and IOI, result in a value between (-1, inf) @@ -399,6 +503,7 @@ def get_phrase_end(m_score, unique_onset_idxs): def phrasing_attributes(m_score, unique_onset_idxs, plot=False): """ + Unfinished! after finishing will update to phrasing_feature rubato: Model the final tempo curve (last 2 measures) using Friberg & Sundberg’s kinematic model: (https://www.researchgate.net/publication/220723460_Evidence_for_Pianist-specific_Rubato_Style_in_Chopin_Nocturnes) From d7f316593d10da93bd0b125f1f1fe46e4119fde2 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Tue, 16 May 2023 10:44:10 +0200 Subject: [PATCH 030/122] minor fix --- partitura/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/__init__.py b/partitura/__init__.py index f2080b2b..074b8726 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -27,7 +27,7 @@ from .musicanalysis import make_note_features, compute_note_array, full_note_array # define a version variable -# __version__ = pkg_resources.get_distribution("partitura").version +__version__ = pkg_resources.get_distribution("partitura").version #: An example MusicXML file for didactic purposes EXAMPLE_MUSICXML = pkg_resources.resource_filename( From edddd61c6f7ec8fc1fdb5c84fa979806670966e8 Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 16 May 2023 11:52:02 +0200 Subject: [PATCH 031/122] reordering and commenting functions in performance_codec - alignment related utility from utils.music goes to performance_codec - add some aliases in note_features for completeness - reorder and comment functions in performance_codec to have the organized by: full codec / time and art codecs / alignment processing / sequence utility / tempo normalization --- partitura/musicanalysis/note_features.py | 12 +- partitura/musicanalysis/performance_codec.py | 318 ++++++++++++++----- partitura/utils/music.py | 138 -------- 3 files changed, 243 insertions(+), 225 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index 58d0f9d0..8a151186 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -15,13 +15,15 @@ __all__ = [ "list_note_feats_functions", + "list_note_feature_functions", + "print_note_feats_functions", + "print_note_feature_functions", + "make_note_feats", "make_note_features", - "make_note_feats", - "full_note_array", - "compute_note_array", - "full_note_array", "make_rest_feats", "make_rest_features", + "compute_note_array", + "full_note_array", ] @@ -300,6 +302,8 @@ def make_rest_features( # alias make_note_feats = make_note_features make_rest_feats = make_rest_features +list_note_feature_functions = list_note_feats_functions +print_note_feature_functions = print_note_feats_functions def compute_note_array( diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index ee6e0a88..453cd996 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -31,6 +31,9 @@ def __init__(self): __all__ = ["encode_performance", "decode_performance", "to_matched_score"] +#### Full Codecs #### + + @deprecated_alias(part='score', ppart="performance") def encode_performance( score: ScoreLike, @@ -204,6 +207,9 @@ def decode_performance( return ppart +#### Time and Articulation Codecs #### + + def decode_time(score_onsets, score_durations, parameters, @@ -263,6 +269,30 @@ def decode_time(score_onsets, return performance +def encode_articulation( + score_durations, performed_durations, unique_onset_idxs, beat_period +): + """ + Encode articulation + """ + articulation = np.zeros_like(score_durations) + for idx, bp in zip(unique_onset_idxs, beat_period): + + sd = score_durations[idx] + pd = performed_durations[idx] + + # indices of notes with duration 0 (grace notes) + grace_mask = sd <= 0 + + # Grace notes have an articulation ratio of 1 + sd[grace_mask] = 1 + pd[grace_mask] = bp + # Compute log articulation ratio + articulation[idx] = np.log2(pd / (bp * sd)) + + return articulation + + def decode_articulation(score_durations, articulation_parameter, beat_period): """ Decode articulation @@ -558,75 +588,8 @@ def tempo_by_derivative(score_onsets, performed_onsets, return tempo_curve, input_onsets -def get_unique_seq(onsets, offsets, unique_onset_idxs=None, return_diff=False): - """ - Get unique onsets of a sequence of notes - """ - eps = np.finfo(float).eps - - first_time = np.min(onsets) - - # ensure last score time is later than last onset - last_time = max(np.max(onsets) + eps, np.max(offsets)) - - total_dur = last_time - first_time - - if unique_onset_idxs is None: - # unique_onset_idxs = unique_onset_idx(score[:, 0]) - unique_onset_idxs = get_unique_onset_idxs(onsets) - - u_onset = np.array([np.mean(onsets[uix]) for uix in unique_onset_idxs]) - # add last offset, so we have as many IOIs as notes - u_onset = np.r_[u_onset, last_time] - - output_dict = dict( - u_onset=u_onset, total_dur=total_dur, unique_onset_idxs=unique_onset_idxs - ) - - if return_diff: - output_dict["diff_u_onset"] = np.diff(u_onset) - - return output_dict - - -def get_unique_onset_idxs( - onsets, eps: float = 1e-6, return_unique_onsets: bool = False -): - """ - Get unique onsets and their indices. - Parameters - ---------- - onsets : np.ndarray - Score onsets in beats. - eps : float - Small epsilon (for dealing with quantization in symbolic scores). - This is particularly useful for dealing with triplets and other - similar rhytmical structures that do not have a finite decimal - representation. - return_unique_onsets : bool (optional) - If `True`, returns the unique score onsets. - Returns - ------- - unique_onset_idxs : np.ndarray - Indices of the unique onsets in the score. - unique_onsets : np.ndarray - Unique score onsets - """ - # Do not assume that the onsets are sorted - # (use a stable sorting algorithm for preserving the order - # of elements with the same onset, which is useful e.g. if the - # notes within a same onset are ordered by pitch) - sort_idx = np.argsort(onsets, kind="mergesort") - split_idx = np.where(np.diff(onsets[sort_idx]) > eps)[0] + 1 - unique_onset_idxs = np.split(sort_idx, split_idx) - - if return_unique_onsets: - # Instead of np.unique(onsets) - unique_onsets = np.array([onsets[uix].mean() for uix in unique_onset_idxs]) +#### Alignment Processing #### - return unique_onset_idxs, unique_onsets - else: - return unique_onset_idxs @deprecated_alias(part='score', ppart="performance") def to_matched_score(score: ScoreLike, @@ -704,28 +667,216 @@ def to_matched_score(score: ScoreLike, return np.array(ms, dtype=fields), snote_ids -def encode_articulation( - score_durations, performed_durations, unique_onset_idxs, beat_period +def get_time_maps_from_alignment( + ppart_or_note_array, spart_or_note_array, alignment, remove_ornaments=True ): """ - Encode articulation + Get time maps to convert performance time (in seconds) to score time (in beats) + and visceversa. + + Parameters + ---------- + ppart_or_note_array : PerformedPart or structured array + The performance information as either PerformedPart or the + note_array generated from such an object. + spart_or_note_array : Part or structured array + Score information as either a Part object or the note array + generated from such an object. + alignment : list + The score--performance alignment, a list of dictionaries. + (see `partitura.io.importmatch.alignment_from_matchfile` for reference) + remove_ornaments : bool (optional) + Whether to consider or not ornaments (including grace notes) + + Returns + ------- + ptime_to_stime_map : scipy.interpolate.interp1d + An instance of interp1d (a callable) that maps performance time (in seconds) + to score time (in beats). + stime_to_ptime_map : scipy.interpolate.interp1d + An instance of inter1d (a callable) that maps score time (in beats) to + performance time (in seconds). + + Note + ---- + This methods uses the average value of the score onsets of notes that are + written in the score as part of a chord (i.e., which start at the same time). """ - articulation = np.zeros_like(score_durations) - for idx, bp in zip(unique_onset_idxs, beat_period): + # Ensure that we are using structured note arrays + perf_note_array = ensure_notearray(ppart_or_note_array) + score_note_array = ensure_notearray(spart_or_note_array) + + # Get indices of the matched notes (notes in the score + # for which there is a performance note + match_idx = get_matched_notes(score_note_array, perf_note_array, alignment) + + # Get onsets and durations + score_onsets = score_note_array[match_idx[:, 0]]["onset_beat"] + score_durations = score_note_array[match_idx[:, 0]]["duration_beat"] + + perf_onsets = perf_note_array[match_idx[:, 1]]["onset_sec"] + + # Use only unique onsets + score_unique_onsets = np.unique(score_onsets) + + # Remove grace notes + if remove_ornaments: + # TODO: check that all onsets have a duration? + # ornaments (grace notes) do not have a duration + score_unique_onset_idxs = np.array( + [ + np.where(np.logical_and(score_onsets == u, score_durations > 0))[0] + for u in score_unique_onsets + ], + dtype=object, + ) - sd = score_durations[idx] - pd = performed_durations[idx] + else: + score_unique_onset_idxs = np.array( + [np.where(score_onsets == u)[0] for u in score_unique_onsets], + dtype=object, + ) - # indices of notes with duration 0 (grace notes) - grace_mask = sd <= 0 + # For chords, we use the average performed onset as a proxy for + # representing the "performeance time" of the position of the score + # onsets + eq_perf_onsets = np.array( + [np.mean(perf_onsets[u]) for u in score_unique_onset_idxs] + ) - # Grace notes have an articulation ratio of 1 - sd[grace_mask] = 1 - pd[grace_mask] = bp - # Compute log articulation ratio - articulation[idx] = np.log2(pd / (bp * sd)) + # Get maps + ptime_to_stime_map = interp1d( + x=eq_perf_onsets, + y=score_unique_onsets, + bounds_error=False, + fill_value="extrapolate", + ) + stime_to_ptime_map = interp1d( + y=eq_perf_onsets, + x=score_unique_onsets, + bounds_error=False, + fill_value="extrapolate", + ) - return articulation + return ptime_to_stime_map, stime_to_ptime_map + + +def get_matched_notes(spart_note_array, ppart_note_array, alignment): + """ + Get the indices of the matched notes in an alignment + + Parameters + ---------- + spart_note_array : structured numpy array + note_array of the score part + ppart_note_array : structured numpy array + note_array of the performed part + alignment : list + The score--performance alignment, a list of dictionaries. + (see `partitura.io.importmatch.alignment_from_matchfile` for reference) + + Returns + ------- + matched_idxs : np.ndarray + A 2D array containing the indices of the matched score and + performed notes, where the columns are + (index_in_score_note_array, index_in_performance_notearray) + """ + # Get matched notes + matched_idxs = [] + for al in alignment: + # Get only matched notes (i.e., ignore inserted or deleted notes) + if al["label"] == "match": + + # if ppart_note_array['id'].dtype != type(al['performance_id']): + if not isinstance(ppart_note_array["id"], type(al["performance_id"])): + p_id = str(al["performance_id"]) + else: + p_id = al["performance_id"] + + p_idx = int(np.where(ppart_note_array["id"] == p_id)[0]) + + s_idx = np.where(spart_note_array["id"] == al["score_id"])[0] + + if len(s_idx) > 0: + s_idx = int(s_idx) + matched_idxs.append((s_idx, p_idx)) + + return np.array(matched_idxs) + + +#### Sequence Processing: onset-wise/note-wise/monotonicity/uniqueness #### + + +def get_unique_seq(onsets, offsets, unique_onset_idxs=None, return_diff=False): + """ + Get unique onsets of a sequence of notes + """ + eps = np.finfo(float).eps + + first_time = np.min(onsets) + + # ensure last score time is later than last onset + last_time = max(np.max(onsets) + eps, np.max(offsets)) + + total_dur = last_time - first_time + + if unique_onset_idxs is None: + # unique_onset_idxs = unique_onset_idx(score[:, 0]) + unique_onset_idxs = get_unique_onset_idxs(onsets) + + u_onset = np.array([np.mean(onsets[uix]) for uix in unique_onset_idxs]) + # add last offset, so we have as many IOIs as notes + u_onset = np.r_[u_onset, last_time] + + output_dict = dict( + u_onset=u_onset, total_dur=total_dur, unique_onset_idxs=unique_onset_idxs + ) + + if return_diff: + output_dict["diff_u_onset"] = np.diff(u_onset) + + return output_dict + + +def get_unique_onset_idxs( + onsets, eps: float = 1e-6, return_unique_onsets: bool = False +): + """ + Get unique onsets and their indices. + Parameters + ---------- + onsets : np.ndarray + Score onsets in beats. + eps : float + Small epsilon (for dealing with quantization in symbolic scores). + This is particularly useful for dealing with triplets and other + similar rhytmical structures that do not have a finite decimal + representation. + return_unique_onsets : bool (optional) + If `True`, returns the unique score onsets. + Returns + ------- + unique_onset_idxs : np.ndarray + Indices of the unique onsets in the score. + unique_onsets : np.ndarray + Unique score onsets + """ + # Do not assume that the onsets are sorted + # (use a stable sorting algorithm for preserving the order + # of elements with the same onset, which is useful e.g. if the + # notes within a same onset are ordered by pitch) + sort_idx = np.argsort(onsets, kind="mergesort") + split_idx = np.where(np.diff(onsets[sort_idx]) > eps)[0] + 1 + unique_onset_idxs = np.split(sort_idx, split_idx) + + if return_unique_onsets: + # Instead of np.unique(onsets) + unique_onsets = np.array([onsets[uix].mean() for uix in unique_onset_idxs]) + + return unique_onset_idxs, unique_onsets + else: + return unique_onset_idxs def monotonize_times(s, deltas=None): @@ -801,6 +952,7 @@ def onsetwise_to_notewise(onsetwise_input, unique_onset_idxs): #### Temo Parameter Normalizations #### + def bp_scale(beat_period): return [beat_period] diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 40161d6a..bdc33d86 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3058,144 +3058,6 @@ def performance_notearray_from_score_notearray( return pnote_array -def get_time_maps_from_alignment( - ppart_or_note_array, spart_or_note_array, alignment, remove_ornaments=True -): - """ - Get time maps to convert performance time (in seconds) to score time (in beats) - and visceversa. - - Parameters - ---------- - ppart_or_note_array : PerformedPart or structured array - The performance information as either PerformedPart or the - note_array generated from such an object. - spart_or_note_array : Part or structured array - Score information as either a Part object or the note array - generated from such an object. - alignment : list - The score--performance alignment, a list of dictionaries. - (see `partitura.io.importmatch.alignment_from_matchfile` for reference) - remove_ornaments : bool (optional) - Whether to consider or not ornaments (including grace notes) - - Returns - ------- - ptime_to_stime_map : scipy.interpolate.interp1d - An instance of interp1d (a callable) that maps performance time (in seconds) - to score time (in beats). - stime_to_ptime_map : scipy.interpolate.interp1d - An instance of inter1d (a callable) that maps score time (in beats) to - performance time (in seconds). - - Note - ---- - This methods uses the average value of the score onsets of notes that are - written in the score as part of a chord (i.e., which start at the same time). - """ - # Ensure that we are using structured note arrays - perf_note_array = ensure_notearray(ppart_or_note_array) - score_note_array = ensure_notearray(spart_or_note_array) - - # Get indices of the matched notes (notes in the score - # for which there is a performance note - match_idx = get_matched_notes(score_note_array, perf_note_array, alignment) - - # Get onsets and durations - score_onsets = score_note_array[match_idx[:, 0]]["onset_beat"] - score_durations = score_note_array[match_idx[:, 0]]["duration_beat"] - - perf_onsets = perf_note_array[match_idx[:, 1]]["onset_sec"] - - # Use only unique onsets - score_unique_onsets = np.unique(score_onsets) - - # Remove grace notes - if remove_ornaments: - # TODO: check that all onsets have a duration? - # ornaments (grace notes) do not have a duration - score_unique_onset_idxs = np.array( - [ - np.where(np.logical_and(score_onsets == u, score_durations > 0))[0] - for u in score_unique_onsets - ], - dtype=object, - ) - - else: - score_unique_onset_idxs = np.array( - [np.where(score_onsets == u)[0] for u in score_unique_onsets], - dtype=object, - ) - - # For chords, we use the average performed onset as a proxy for - # representing the "performeance time" of the position of the score - # onsets - eq_perf_onsets = np.array( - [np.mean(perf_onsets[u]) for u in score_unique_onset_idxs] - ) - - # Get maps - ptime_to_stime_map = interp1d( - x=eq_perf_onsets, - y=score_unique_onsets, - bounds_error=False, - fill_value="extrapolate", - ) - stime_to_ptime_map = interp1d( - y=eq_perf_onsets, - x=score_unique_onsets, - bounds_error=False, - fill_value="extrapolate", - ) - - return ptime_to_stime_map, stime_to_ptime_map - - -def get_matched_notes(spart_note_array, ppart_note_array, alignment): - """ - Get the indices of the matched notes in an alignment - - Parameters - ---------- - spart_note_array : structured numpy array - note_array of the score part - ppart_note_array : structured numpy array - note_array of the performed part - alignment : list - The score--performance alignment, a list of dictionaries. - (see `partitura.io.importmatch.alignment_from_matchfile` for reference) - - Returns - ------- - matched_idxs : np.ndarray - A 2D array containing the indices of the matched score and - performed notes, where the columns are - (index_in_score_note_array, index_in_performance_notearray) - """ - # Get matched notes - matched_idxs = [] - for al in alignment: - # Get only matched notes (i.e., ignore inserted or deleted notes) - if al["label"] == "match": - - # if ppart_note_array['id'].dtype != type(al['performance_id']): - if not isinstance(ppart_note_array["id"], type(al["performance_id"])): - p_id = str(al["performance_id"]) - else: - p_id = al["performance_id"] - - p_idx = int(np.where(ppart_note_array["id"] == p_id)[0]) - - s_idx = np.where(spart_note_array["id"] == al["score_id"])[0] - - if len(s_idx) > 0: - s_idx = int(s_idx) - matched_idxs.append((s_idx, p_idx)) - - return np.array(matched_idxs) - - def generate_random_performance_note_array( num_notes: int = 20, rng: Union[int, np.random.RandomState] = np.random.RandomState(1984), From d3e73516b394858e91918d54446eec5f71556a7c Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 16 May 2023 12:13:43 +0200 Subject: [PATCH 032/122] fix imports so tests check --- partitura/io/exportmatch.py | 5 ++++- partitura/musicanalysis/performance_codec.py | 1 + tests/test_utils.py | 7 ++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index ab332aae..92072d56 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -45,7 +45,6 @@ from partitura.utils.music import ( seconds_to_midi_ticks, - get_time_maps_from_alignment, ) from partitura.utils.misc import ( @@ -54,6 +53,10 @@ deprecated_parameter, ) +from partitura.musicanalysis.performance_codec import ( + get_time_maps_from_alignment +) + __all__ = ["save_match"] diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 453cd996..60fb887a 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -26,6 +26,7 @@ def __init__(self): from partitura.musicanalysis import note_features from partitura.utils.misc import deprecated_alias from partitura.utils.generic import interp1d +from partitura.utils.music import ensure_notearray from scipy.misc import derivative __all__ = ["encode_performance", "decode_performance", "to_matched_score"] diff --git a/tests/test_utils.py b/tests/test_utils.py index 8574a11c..cc41ec15 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -8,6 +8,7 @@ import numpy as np from partitura.utils import music +from partitura.musicanalysis import performance_codec from tests import MATCH_IMPORT_EXPORT_TESTFILES, VOSA_TESTFILES, MOZART_VARIATION_FILES from scipy.interpolate import interp1d as scinterp1d @@ -25,7 +26,7 @@ def test_get_matched_notes(self): ) perf_note_array = perf.note_array() scr_note_array = scr.note_array() - matched_idxs = music.get_matched_notes( + matched_idxs = performance_codec.get_matched_notes( spart_note_array=scr_note_array, ppart_note_array=perf_note_array, alignment=alignment, @@ -53,7 +54,7 @@ def test_get_time_maps_from_alignment(self): ( ptime_to_stime_map, stime_to_ptime_map, - ) = music.get_time_maps_from_alignment( + ) = performance_codec.get_time_maps_from_alignment( spart_or_note_array=scr[0], ppart_or_note_array=ppart, alignment=alignment, @@ -94,7 +95,7 @@ def test_performance_from_part(self): for sid in snote_array["id"] ] - matched_idxs = music.get_matched_notes( + matched_idxs = performance_codec.get_matched_notes( spart_note_array=snote_array, ppart_note_array=pnote_array, alignment=alignment, From da904aba7a0c1abfefb3043d148c0c46d372c91d Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 16 May 2023 12:58:11 +0200 Subject: [PATCH 033/122] fix typo and spacing --- partitura/musicanalysis/performance_features.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 6bc5b015..8e4e7f5c 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -235,7 +235,7 @@ def parse_changing_ramp(unique_onset_idxs, m_score): ### Asynchrony -def asynchrnoy_feature(m_score: list, +def asynchrony_feature(m_score: list, unique_onset_idxs: list, v=False): """ @@ -375,6 +375,7 @@ def dynamics_feature(m_score : list, ### Articulation + def get_next_note(note_info, match_voiced): """get the next note in the same voice that's a resonalble transition note_info: the row of current note @@ -394,6 +395,7 @@ def get_next_note(note_info, match_voiced): return next_position_notes[closest_idx] + def articulation_feature(m_score : list, unique_onset_idxs: list, return_mask=False): @@ -454,6 +456,7 @@ def articulation_feature(m_score : list, else: return kor_ + def get_kor(e1, e2): kot = e1['p_offset'] - e2['p_onset'] @@ -468,6 +471,7 @@ def get_kor(e1, e2): ### Phrasing + def freiberg_kinematic(params, xdata, ydata): w, q = params return ydata - (1 + (w ** q - 1) * xdata) ** (1/q) @@ -501,6 +505,7 @@ def get_phrase_end(m_score, unique_onset_idxs): endings = [(xdata, ydata)] return endings + def phrasing_attributes(m_score, unique_onset_idxs, plot=False): """ Unfinished! after finishing will update to phrasing_feature From 06c70b2748187ff347f9fb21bf7ec72eab112baf Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Tue, 16 May 2023 15:14:59 +0200 Subject: [PATCH 034/122] tmp save --- partitura/__init__.py | 2 +- .../musicanalysis/performance_features.py | 123 ++++++++++++++---- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/partitura/__init__.py b/partitura/__init__.py index 074b8726..f2080b2b 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -27,7 +27,7 @@ from .musicanalysis import make_note_features, compute_note_array, full_note_array # define a version variable -__version__ = pkg_resources.get_distribution("partitura").version +# __version__ = pkg_resources.get_distribution("partitura").version #: An example MusicXML file for didactic purposes EXAMPLE_MUSICXML = pkg_resources.resource_filename( diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 6bc5b015..a88a5753 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -17,6 +17,7 @@ import numpy.lib.recfunctions as rfn from partitura.score import ScoreLike from partitura.performance import PerformanceLike +from partitura.utils.generic import interp1d from partitura.musicanalysis.performance_codec import to_matched_score, onsetwise_to_notewise, encode_tempo @@ -79,7 +80,7 @@ def compute_performance_features(score: ScoreLike, func = bf else: warnings.warn("Ignoring unknown performance feature function {}".format(bf)) - features = func(m_score, unique_onset_idxs) + features = func(m_score, unique_onset_idxs, performance) # check if the size and number of the feature function are correct if features.size != 0: n_notes = len(m_score) @@ -237,6 +238,7 @@ def parse_changing_ramp(unique_onset_idxs, m_score): ### Asynchrony def asynchrnoy_feature(m_score: list, unique_onset_idxs: list, + performance: PerformanceLike, v=False): """ Compute the asynchrony attributes from the alignment. @@ -252,10 +254,10 @@ def asynchrnoy_feature(m_score: list, ------- async_ : structured array structured array (broadcasted to the note level) with the following fields - delta: the largest time difference (in seconds) between onsets in this group [0, 1] - pitch_cor: correlation between timing and pitch [-1, 1] - vel_cor: correlation between timing and velocity [-1, 1] - voice_std: std of the avg timing (in seconds) of each voice in this group [0, 1] + delta [0, 1]: the largest time difference (in seconds) between onsets in this group + pitch_cor [-1, 1]: correlation between timing and pitch + vel_cor [-1, 1]: correlation between timing and velocity + voice_std [0, 1]: std of the avg timing (in seconds) of each voice in this group """ async_ = np.zeros(len(unique_onset_idxs), dtype=[( @@ -293,6 +295,7 @@ def asynchrnoy_feature(m_score: list, ### Dynamics def dynamics_feature(m_score : list, unique_onset_idxs: list, + performance: PerformanceLike, window : int = 3, agg : str = "avg"): """ @@ -375,27 +378,9 @@ def dynamics_feature(m_score : list, ### Articulation -def get_next_note(note_info, match_voiced): - """get the next note in the same voice that's a resonalble transition - note_info: the row of current note - match_voiced: all notes in the same voice - """ - - next_position = min(o for o in match_voiced['onset'] if o > note_info['onset']) - - # if the next note is not immediate successor of the previous one... - if next_position != note_info['onset'] + note_info['duration']: - return None - - next_position_notes = match_voiced[match_voiced['onset'] == next_position] - - # from the notes in the next position, find the one that's closest pitch-wise. - closest_idx = np.abs((next_position_notes['pitch'] - note_info['pitch'])).argmin() - - return next_position_notes[closest_idx] - def articulation_feature(m_score : list, unique_onset_idxs: list, + performance: PerformanceLike, return_mask=False): """ Compute the articulation attributes (key overlap ratio) from the alignment. @@ -465,6 +450,95 @@ def get_kor(e1, e2): return min(kor, 5) +def get_next_note(note_info, match_voiced): + """get the next note in the same voice that's a resonalble transition + note_info: the row of current note + match_voiced: all notes in the same voice + """ + + next_position = min(o for o in match_voiced['onset'] if o > note_info['onset']) + + # if the next note is not immediate successor of the previous one... + if next_position != note_info['onset'] + note_info['duration']: + return None + + next_position_notes = match_voiced[match_voiced['onset'] == next_position] + + # from the notes in the next position, find the one that's closest pitch-wise. + closest_idx = np.abs((next_position_notes['pitch'] - note_info['pitch'])).argmin() + + return next_position_notes[closest_idx] + + +### Pedals + +def pedal_ramp(ppart: PerformedPart): + """Pedal ramp. + + Returns: + * pramp : a ramp function that ranges from 0 + to 127 with the change of sustain pedal + """ + pedal_controls = ppart.controls + W = np.zeros((len(onsets), 2)) + + for slur in slurs: + if not slur.end: + continue + x = [slur.start.t, slur.end.t] + y_inc = [0, 1] + y_dec = [1, 0] + W[:, 0] += interp1d(x, y_inc, bounds_error=False, fill_value=0)(onsets) + W[:, 1] += interp1d(x, y_dec, bounds_error=False, fill_value=0)(onsets) + + # Filter out NaN values + W[np.isnan(W)] = 0.0 + return W + + +def pedal_feature(m_score : list, + unique_onset_idxs: list, + performance: PerformanceLike): + """ + Compute the pedal features. + + Parameters + ---------- + m_score : structured array + correspondance between score and performance notes, with score markings. + + Returns + ------- + pedal_ : structured array (4, n_notes) with fields + onset_value [0, 127]: + offset_value [0, 127]: + prev_release_time : + next_release_time: + """ + pedal_ramp(performance.performedparts[0]) + hook() + kor_ = np.full(len(m_score), -1, dtype=[("kor", "f4")]) + + # consider the note transition by each voice + for voice in np.unique(m_score['voice']): + match_voiced = m_score[m_score['voice'] == voice] + for _, note_info in enumerate(match_voiced): + + if note_info['onset'] == match_voiced['onset'].max(): # last beat + break + next_note_info = get_next_note(note_info, match_voiced) # find most plausible transition + + if next_note_info: # in some cases no meaningful transition + j = np.where(m_score == note_info)[0].item() # original position + + if (note_info['offset'] == next_note_info['onset']): + kor_[j]['kor'] = get_kor(note_info, next_note_info) + + + + return kor_ + + ### Phrasing @@ -550,3 +624,4 @@ def phrasing_attributes(m_score, unique_onset_idxs, plot=False): plt.show() return phrasing_ + From e21847a282477ccdbcbbe9d2f2679dd3503023ae Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 16 May 2023 17:27:01 +0200 Subject: [PATCH 035/122] performance_features: function naming, print funcs, dummy test --- partitura/musicanalysis/note_features.py | 2 +- .../musicanalysis/performance_features.py | 34 +- tests/data/match/Chopin_op10_no3_p01.match | 3849 +++++++++++++++++ tests/test_performance_expressions.py | 25 + 4 files changed, 3904 insertions(+), 6 deletions(-) create mode 100644 tests/data/match/Chopin_op10_no3_p01.match diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index 8a151186..62bf24c3 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -51,7 +51,7 @@ def list_note_feats_functions(): """Return a list of all feature function names defined in this module. The feature function names listed here can be specified by name in - the `make_feature` function. For example: + the `make_note_features` and `make_rest_features` functions. For example: >>> feature, names = make_note_feats(part, ['metrical_feature', 'articulation_feature']) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 8e4e7f5c..00cc0d4a 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -63,10 +63,10 @@ def compute_performance_features(score: ScoreLike, acc = [] if isinstance(feature_functions, str) and feature_functions == "all": - feature_functions = list_note_feats_functions() + feature_functions = list_performance_feats_functions() elif not isinstance(feature_functions, list): raise TypeError( - "feature_functions variable {} needs to be list or all".format( + "feature_functions variable {} needs to be list or 'all'".format( feature_functions ) ) @@ -157,13 +157,16 @@ def compute_matched_score(score: ScoreLike, return m_score, unique_onset_idxs -def list_note_feats_functions(): +def list_performance_feats_functions(): """Return a list of all feature function names defined in this module. The feature function names listed here can be specified by name in - the `make_feature` function. For example: + the `compute_performance_features` function. For example: - >>> feature, names = make_note_feats(part, ['metrical_feature', 'articulation_feature']) + >>> feature, names = compute_performance_features(score, + performance, + alignment, + ['asynchrony_feature']) Returns ------- @@ -183,6 +186,27 @@ def list_note_feats_functions(): return bfs +def print_performance_feats_functions(): + """Print a list of all feature function names defined in this module, + with descriptions where available. + + """ + module = sys.modules[__name__] + doc_indent = 4 + for name in list_performance_feats_functions(): + print("* {}".format(name)) + member = getattr(sys.modules[__name__], name) + if member.__doc__: + print( + " " * doc_indent + member.__doc__.replace("\n", " " * doc_indent + "\n") + ) + + + +# alias +list_performance_feature_functions = list_performance_feats_functions +print_performance_feature_functions = print_performance_feats_functions + def map_fields(note_info, fields): """ map the one-hot fields of dynamics and articulation marking into one column with field. diff --git a/tests/data/match/Chopin_op10_no3_p01.match b/tests/data/match/Chopin_op10_no3_p01.match new file mode 100644 index 00000000..d2f239c7 --- /dev/null +++ b/tests/data/match/Chopin_op10_no3_p01.match @@ -0,0 +1,3849 @@ +info(matchFileVersion,4.0). +info(piece,Chopin_op10_no3). +info(scoreFileName,Chopin_op10_no3_p01.match). +info(midiFileName,Chopin_op10_no3_p01.mid). +info(composer,Frèdéryk Chopin). +info(performer,Pianist 01). +info(midiClockUnits,4000). +info(midiClockRate,500000). +info(keySignature,[E Maj]). +info(timeSignature,[2/4]). +snote(n1,[b,n],3,0:2,1/8,1/8,-0.5,0.0,[1])-note(0,[b,n],3,39940,42140,46960,44). +snote(n2,[e,n],4,1:1,0,1/8,0.0,0.5,[1])-note(2,[e,n],4,45630,55280,65120,54). +snote(n3,[g,#],3,1:1,0,1/16,0.0,0.25,[3])-note(3,[g,#],3,46210,51830,65120,26). +snote(n4,[e,n],2,1:1,0,1/4,0.0,1.0,[7])-note(1,[e,n],2,45620,60390,65120,22). +snote(n5,[b,n],3,1:1,1/16,1/16,0.25,0.5,[3])-note(5,[b,n],3,51570,56570,65120,37). +snote(n6,[b,n],2,1:1,1/16,1/8,0.25,0.75,[4])-note(4,[b,n],2,51460,55780,65120,20). +snote(n7,[d,#],4,1:1,1/8,1/16,0.5,0.75,[1])-note(6,[d,#],4,55780,60210,65120,52). +snote(n8,[g,#],3,1:1,1/8,1/16,0.5,0.75,[3])-note(7,[g,#],3,56100,58580,65120,32). +snote(n9,[e,n],4,1:1,3/16,1/16,0.75,1.0,[1])-note(8,[e,n],4,59810,64900,65120,59). +snote(n10,[b,n],3,1:1,3/16,1/16,0.75,1.0,[3])-note(9,[b,n],3,60020,64520,65120,41). +snote(n11,[b,n],2,1:1,3/16,1/16,0.75,1.0,[4])-note(10,[b,n],2,60150,64600,65120,26). +snote(n12,[f,#],4,1:2,0,5/16,1.0,2.25,[1])-note(11,[f,#],4,64270,83250,97920,58). +snote(n13,[d,#],4,1:2,0,3/8,1.0,2.5,[2])-note(13,[d,#],4,64550,85870,97920,41). +snote(n14,[a,n],3,1:2,0,1/16,1.0,1.25,[3])-note(14,[a,n],3,64720,69720,69720,32). +snote(n15,[b,n],1,1:2,0,1/4,1.0,2.0,[7])-note(12,[b,n],1,64550,76630,80880,30). +snote(n16,[b,n],3,1:2,1/16,1/16,1.25,1.5,[3])-note(16,[b,n],3,69850,74250,80880,36). +snote(n17,[b,n],2,1:2,1/16,1/8,1.25,1.75,[4])-note(15,[b,n],2,69630,73620,80880,31). +snote(n18,[a,n],3,1:2,1/8,1/16,1.5,1.75,[3])-note(17,[a,n],3,73660,77370,80880,40). +snote(n19,[b,n],3,1:2,3/16,1/16,1.75,2.0,[3])-note(19,[b,n],3,77360,81790,81790,44). +snote(n20,[b,n],2,1:2,3/16,1/16,1.75,2.0,[4])-note(18,[b,n],2,77330,81190,81190,30). +snote(n21,[a,n],3,2:1,0,1/16,2.0,2.25,[3])-note(20,[a,n],3,81100,84910,97920,39). +snote(n22,[b,n],1,2:1,0,1/4,2.0,3.0,[7])-note(21,[b,n],1,81120,94120,97920,30). +snote(n23,[g,#],4,2:1,1/16,1/16,2.25,2.5,[1])-note(22,[g,#],4,85040,87070,97920,57). +snote(n24,[b,n],3,2:1,1/16,1/16,2.25,2.5,[3])-note(23,[b,n],3,85070,86590,97920,44). +snote(n25,[b,n],2,2:1,1/16,1/8,2.25,2.75,[4])-note(24,[b,n],2,85220,88030,97920,31). +snote(n26,[g,#],4,2:1,1/8,1/16,2.5,2.75,[1])-note(25,[g,#],4,88770,91740,97920,63). +snote(n27,[d,#],4,2:1,1/8,1/8,2.5,3.0,[2])-note(26,[d,#],4,88930,93360,97920,48). +snote(n28,[a,n],3,2:1,1/8,1/16,2.5,2.75,[3])-note(27,[a,n],3,89400,91580,97920,11). +snote(n29,[f,#],4,2:1,3/16,1/16,2.75,3.0,[1])-note(28,[f,#],4,92480,94170,97920,65). +snote(n30,[b,n],3,2:1,3/16,1/16,2.75,3.0,[3])-note(29,[b,n],3,92730,93270,97920,45). +snote(n31,[b,n],2,2:1,3/16,1/16,2.75,3.0,[4])-note(30,[b,n],2,92780,94760,97920,31). +snote(n32,[g,#],4,2:2,0,5/16,3.0,4.25,[1])-note(31,[g,#],4,97270,115460,130400,61). +snote(n33,[e,n],4,2:2,0,1/4,3.0,4.0,[2])-note(33,[e,n],4,97500,114040,114040,48). +snote(n34,[g,#],3,2:2,0,1/16,3.0,3.25,[3])-note(34,[g,#],3,97580,102500,113440,33). +snote(n35,[e,n],2,2:2,0,1/4,3.0,4.0,[7])-note(32,[e,n],2,97450,109830,113440,29). +snote(n36,[b,n],3,2:2,1/16,1/16,3.25,3.5,[3])-note(36,[b,n],3,102570,106790,113440,38). +snote(n37,[b,n],2,2:2,1/16,1/8,3.25,3.75,[4])-note(35,[b,n],2,102560,106020,113440,27). +snote(n38,[g,#],3,2:2,1/8,1/16,3.5,3.75,[3])-note(37,[g,#],3,106450,109590,113440,36). +snote(n39,[b,n],3,2:2,3/16,1/16,3.75,4.0,[3])-note(39,[b,n],3,109910,113780,113780,43). +snote(n40,[b,n],2,2:2,3/16,1/16,3.75,4.0,[4])-note(38,[b,n],2,109860,113630,113630,25). +snote(n41,[g,#],3,3:1,0,1/16,4.0,4.25,[3])-note(41,[g,#],3,113700,115950,130400,41). +snote(n42,[e,n],2,3:1,0,1/4,4.0,5.0,[7])-note(40,[e,n],2,113580,125350,130400,27). +snote(n43,[a,n],4,3:1,1/16,1/16,4.25,4.5,[1])-note(42,[a,n],4,117340,119200,130400,57). +snote(n44,[e,n],4,3:1,1/16,1/16,4.25,4.5,[3])-note(44,[e,n],4,117670,118780,130400,42). +snote(n45,[b,n],2,3:1,1/16,1/8,4.25,4.75,[4])-note(43,[b,n],2,117510,119720,130400,33). +snote(n46,[a,n],4,3:1,1/8,1/16,4.5,4.75,[1])-note(45,[a,n],4,121000,124990,130400,68). +snote(n47,[b,n],3,3:1,1/8,1/16,4.5,4.75,[3])-note(46,[b,n],3,121410,123650,130400,44). +snote(n48,[g,#],4,3:1,3/16,1/16,4.75,5.0,[1])-note(47,[g,#],4,124670,126260,130400,72). +snote(n49,[e,n],4,3:1,3/16,1/16,4.75,5.0,[3])-note(48,[e,n],4,124960,125340,130400,45). +snote(n50,[b,n],2,3:1,3/16,1/16,4.75,5.0,[4])-note(49,[b,n],2,124970,127130,130400,34). +snote(n51,[c,#],5,3:2,0,3/16,5.0,5.75,[1])-note(50,[c,#],5,129250,135540,146720,71). +snote(n52,[d,#],4,3:2,0,1/16,5.0,5.25,[3])-note(51,[d,#],4,129490,135340,146720,51). +snote(n53,[b,n],1,3:2,0,1/4,5.0,6.0,[7])-note(52,[b,n],1,129550,142060,146720,36). +snote(n54,[a,n],4,3:2,1/16,1/16,5.25,5.5,[3])-note(54,[a,n],4,134040,135850,146720,56). +snote(n55,[b,n],2,3:2,1/16,1/8,5.25,5.75,[4])-note(53,[b,n],2,133890,136630,146720,34). +snote(n56,[b,n],3,3:2,1/8,1/16,5.5,5.75,[3])-note(55,[b,n],3,137780,140480,146720,49). +snote(n57,[b,n],4,3:2,3/16,1/16,5.75,6.0,[1])-note(56,[b,n],4,141230,142200,146720,60). +snote(n58,[d,#],4,3:2,3/16,1/16,5.75,6.0,[3])-note(57,[d,#],4,141470,142220,146720,44). +snote(n59,[b,n],2,3:2,3/16,1/16,5.75,6.0,[4])-note(58,[b,n],2,141540,143270,146720,34). +snote(n60,[a,n],4,4:1,0,1/16,6.0,6.25,[1])-note(59,[a,n],4,145510,150400,150400,59). +snote(n61,[b,n],3,4:1,0,1/16,6.0,6.25,[3])-note(61,[b,n],3,145780,149340,149340,42). +snote(n62,[e,n],2,4:1,0,1/4,6.0,7.0,[7])-note(60,[e,n],2,145610,158320,161600,30). +snote(n63,[g,#],4,4:1,1/16,1/16,6.25,6.5,[1])-note(62,[g,#],4,149580,151760,156880,52). +snote(n64,[e,n],4,4:1,1/16,1/16,6.25,6.5,[3])-note(63,[e,n],4,149730,150890,156880,36). +snote(n65,[b,n],2,4:1,1/16,1/8,6.25,6.75,[4])-note(64,[b,n],2,149840,152800,156880,11). +snote(n66,[d,#],4,4:1,1/8,1/16,6.5,6.75,[1])-note(65,[d,#],4,153100,156670,156880,53). +snote(n67,[g,#],3,4:1,1/8,1/16,6.5,6.75,[3])-note(66,[g,#],3,153490,155610,156880,39). +snote(n68,[e,n],4,4:1,3/16,1/16,6.75,7.0,[1])-note(67,[e,n],4,156810,161700,161700,55). +snote(n69,[b,n],3,4:1,3/16,1/16,6.75,7.0,[3])-note(68,[b,n],3,156950,158350,161600,39). +snote(n70,[b,n],2,4:1,3/16,1/16,6.75,7.0,[4])-note(69,[b,n],2,157040,160560,161600,29). +snote(n71,[f,#],4,4:2,0,5/16,7.0,8.25,[1])-note(70,[f,#],4,161500,179640,179640,54). +snote(n72,[c,#],4,4:2,0,1/16,7.0,7.25,[2])-note(72,[c,#],4,161960,167190,167280,34). +snote(n73,[a,n],3,4:2,0,1/16,7.0,7.25,[3])-note(73,[a,n],3,162010,164240,167280,27). +snote(n74,[b,n],1,4:2,0,1/4,7.0,8.0,[7])-note(71,[b,n],1,161730,175760,178080,25). +snote(n75,[d,#],4,4:2,1/16,1/16,7.25,7.5,[2])-note(74,[d,#],4,167060,170840,174560,41). +snote(n76,[b,n],3,4:2,1/16,1/16,7.25,7.5,[3])-note(76,[b,n],3,167650,169160,169160,17). +snote(n77,[b,n],2,4:2,1/16,1/8,7.25,7.75,[4])-note(75,[b,n],2,167200,171160,174560,29). +snote(n78,[c,#],4,4:2,1/8,1/16,7.5,7.75,[2])-note(77,[c,#],4,170980,174810,174810,42). +snote(n79,[a,n],3,4:2,1/8,1/16,7.5,7.75,[3])-note(78,[a,n],3,171320,171940,174560,30). +snote(n80,[d,#],4,4:2,3/16,1/16,7.75,8.0,[2])-note(80,[d,#],4,174670,178160,178160,41). +snote(n81,[b,n],3,4:2,3/16,1/16,7.75,8.0,[3])-note(81,[b,n],3,174920,175730,178080,26). +snote(n82,[b,n],2,4:2,3/16,1/16,7.75,8.0,[4])-note(79,[b,n],2,174620,178460,178460,34). +snote(n83,[c,#],4,5:1,0,1/16,8.0,8.25,[2])-note(83,[c,#],4,178350,182380,182720,38). +snote(n84,[a,n],3,5:1,0,1/16,8.0,8.25,[3])-note(84,[a,n],3,178530,179500,179500,31). +snote(n85,[b,n],1,5:1,0,1/4,8.0,9.0,[7])-note(82,[b,n],1,178340,191130,194800,31). +snote(n86,[g,#],4,5:1,1/16,1/16,8.25,8.5,[1])-note(85,[g,#],4,182560,185450,194800,55). +snote(n87,[d,#],4,5:1,1/16,1/16,8.25,8.5,[2])-note(86,[d,#],4,182660,185840,194800,40). +snote(n88,[b,n],3,5:1,1/16,1/16,8.25,8.5,[3])-note(88,[b,n],3,182830,184310,184310,34). +snote(n89,[b,n],2,5:1,1/16,1/8,8.25,8.75,[4])-note(87,[b,n],2,182770,186610,194800,31). +snote(n90,[g,#],4,5:1,1/8,1/16,8.5,8.75,[1])-note(89,[g,#],4,186310,189250,194800,60). +snote(n91,[c,#],4,5:1,1/8,1/16,8.5,8.75,[2])-note(90,[c,#],4,186340,188090,194800,46). +snote(n92,[a,n],3,5:1,1/8,1/16,8.5,8.75,[3])-note(91,[a,n],3,186490,187120,194800,38). +snote(n93,[f,#],4,5:1,3/16,1/16,8.75,9.0,[1])-note(92,[f,#],4,190020,191400,194800,57). +snote(n94,[d,#],4,5:1,3/16,1/16,8.75,9.0,[2])-note(94,[d,#],4,190240,191150,194800,45). +snote(n95,[b,n],3,5:1,3/16,1/16,8.75,9.0,[3])-note(93,[b,n],3,190200,190650,194800,42). +snote(n96,[b,n],2,5:1,3/16,1/16,8.75,9.0,[4])-note(95,[b,n],2,190300,194130,194800,37). +snote(n97,[e,n],4,5:2,0,1/4,9.0,10.0,[1])-note(96,[e,n],4,194370,207290,213120,57). +snote(n98,[g,#],3,5:2,0,1/16,9.0,9.25,[3])-note(98,[g,#],3,194890,198940,198940,25). +snote(n99,[e,n],2,5:2,0,1/4,9.0,10.0,[7])-note(97,[e,n],2,194550,205150,213120,28). +snote(n100,[b,n],3,5:2,1/16,1/16,9.25,9.5,[3])-note(100,[b,n],3,198750,202960,213120,37). +snote(n101,[b,n],2,5:2,1/16,1/8,9.25,9.75,[4])-note(99,[b,n],2,198730,201830,213120,23). +snote(n102,[g,#],3,5:2,1/8,1/16,9.5,9.75,[3])-note(101,[g,#],3,202330,204330,213120,36). +snote(n103,[b,n],3,5:2,3/16,1/16,9.75,10.0,[3])-note(102,[b,n],3,206520,207010,213120,28). +snote(n104,[b,n],2,5:2,3/16,1/16,9.75,10.0,[4])-note(103,[b,n],2,206530,207340,213120,26). +snote(n105,[g,#],4,6:1,0,1/16,10.0,10.25,[1])-note(104,[g,#],4,211380,215630,215630,46). +snote(n106,[d,n],4,6:1,0,1/16,10.0,10.25,[3])-note(106,[d,n],4,211610,215430,215430,32). +snote(n107,[e,n],2,6:1,0,1/4,10.0,11.0,[7])-note(105,[e,n],2,211410,224770,226240,28). +snote(n108,[a,n],4,6:1,1/16,1/16,10.25,10.5,[1])-note(107,[a,n],4,215240,218910,226240,50). +snote(n109,[e,n],4,6:1,1/16,1/16,10.25,10.5,[3])-note(109,[e,n],4,215540,218500,226240,40). +snote(n110,[e,n],3,6:1,1/16,1/8,10.25,10.75,[4])-note(108,[e,n],3,215300,218960,226240,35). +snote(n111,[f,#],4,6:1,1/8,1/16,10.5,10.75,[1])-note(110,[f,#],4,218480,222550,226240,55). +snote(n112,[d,n],4,6:1,1/8,1/16,10.5,10.75,[3])-note(111,[d,n],4,218800,220920,226240,40). +snote(n113,[g,#],4,6:1,3/16,1/16,10.75,11.0,[1])-note(112,[g,#],4,221900,223760,226240,62). +snote(n114,[e,n],4,6:1,3/16,1/16,10.75,11.0,[3])-note(114,[e,n],4,222250,222720,226240,31). +snote(n115,[e,n],3,6:1,3/16,1/16,10.75,11.0,[4])-note(113,[e,n],3,221960,224830,226240,39). +snote(n116,[a,n],4,6:2,0,1/16,11.0,11.25,[1])-note(115,[a,n],4,225330,229050,229050,68). +snote(n117,[c,#],4,6:2,0,1/16,11.0,11.25,[3])-note(116,[c,#],4,225620,229230,229230,46). +snote(n118,[a,n],2,6:2,0,1/4,11.0,12.0,[7])-note(117,[a,n],2,225640,235050,239600,34). +snote(n119,[b,n],4,6:2,1/16,1/16,11.25,11.5,[1])-note(118,[b,n],4,228650,231870,239600,70). +snote(n120,[e,n],4,6:2,1/16,1/16,11.25,11.5,[3])-note(120,[e,n],4,228850,232150,239600,57). +snote(n121,[e,n],3,6:2,1/16,1/8,11.25,11.75,[4])-note(119,[e,n],3,228720,231850,239600,40). +snote(n122,[g,#],4,6:2,1/8,1/16,11.5,11.75,[1])-note(121,[g,#],4,231860,235880,239600,68). +snote(n123,[c,#],4,6:2,1/8,1/16,11.5,11.75,[3])-note(122,[c,#],4,231950,235040,239600,61). +snote(n124,[a,n],4,6:2,3/16,1/16,11.75,12.0,[1])-note(123,[a,n],4,235160,239480,239600,72). +snote(n125,[e,n],4,6:2,3/16,1/16,11.75,12.0,[3])-note(125,[e,n],4,235380,237030,239600,47). +snote(n126,[e,n],3,6:2,3/16,1/16,11.75,12.0,[4])-note(124,[e,n],3,235300,239290,239600,40). +snote(n127,[c,#],5,7:1,0,1/8,12.0,12.5,[1])-note(127,[c,#],5,239070,246000,254080,71). +snote(n128,[c,#],4,7:1,0,1/16,12.0,12.25,[3])-note(128,[c,#],4,239370,244770,244770,45). +snote(n129,[a,n],2,7:1,0,1/4,12.0,13.0,[7])-note(126,[a,n],2,238980,253980,254080,38). +snote(n130,[e,n],4,7:1,1/16,1/16,12.25,12.5,[3])-note(130,[e,n],4,243620,247380,254080,48). +snote(n131,[f,#],3,7:1,1/16,1/8,12.25,12.75,[4])-note(129,[f,#],3,243410,247960,254080,55). +snote(n132,[f,#],4,7:1,1/8,1/4,12.5,13.5,[1])-note(131,[f,#],4,247010,257510,267280,64). +snote(n133,[c,#],4,7:1,1/8,1/16,12.5,12.75,[3])-note(132,[c,#],4,247540,250620,254080,34). +snote(n134,[e,n],4,7:1,3/16,1/16,12.75,13.0,[3])-note(133,[e,n],4,250460,254090,254090,53). +snote(n135,[f,#],3,7:1,3/16,1/16,12.75,13.0,[4])-note(134,[f,#],3,250630,253910,254080,39). +snote(n136,[b,n],3,7:2,0,1/16,13.0,13.25,[3])-note(135,[b,n],3,253690,257020,267280,55). +snote(n137,[b,n],2,7:2,0,1/4,13.0,14.0,[7])-note(136,[b,n],2,253760,267650,267650,48). +snote(n138,[e,n],4,7:2,1/16,1/16,13.25,13.5,[3])-note(138,[e,n],4,257030,258360,267280,59). +snote(n139,[f,#],3,7:2,1/16,1/8,13.25,13.75,[4])-note(137,[f,#],3,256860,258730,267280,40). +snote(n140,[a,#],4,7:2,1/8,0,13.5,13.5,[1,grace])-note(140,[a,#],4,259690,261790,267280,63). +snote(n141,[g,#],4,7:2,1/8,1/16,13.5,13.75,[1])-note(141,[g,#],4,260190,264370,267280,60). +snote(n142,[b,n],3,7:2,1/8,1/16,13.5,13.75,[3])-note(139,[b,n],3,259100,261360,267280,45). +snote(n143,[f,#],4,7:2,3/16,3/16,13.75,14.5,[1])-note(142,[f,#],4,263410,271400,282640,61). +snote(n144,[e,n],4,7:2,3/16,1/16,13.75,14.0,[3])-note(144,[e,n],4,263670,267660,267660,53). +snote(n145,[f,#],3,7:2,3/16,1/16,13.75,14.0,[4])-note(143,[f,#],3,263630,267490,267490,39). +snote(n146,[a,#],3,8:1,0,1/16,14.0,14.25,[3])-note(146,[a,#],3,267180,271360,282640,56). +snote(n147,[c,#],3,8:1,0,1/4,14.0,15.0,[7])-note(145,[c,#],3,267140,279190,282640,49). +snote(n148,[e,n],4,8:1,1/16,1/16,14.25,14.5,[3])-note(147,[e,n],4,270530,272240,282640,58). +snote(n149,[f,#],3,8:1,1/16,1/8,14.25,14.75,[4])-note(148,[f,#],3,270650,272610,282640,37). +snote(n150,[a,#],4,8:1,1/8,0,14.5,14.5,[1,grace])-note(150,[a,#],4,273350,275320,282640,61). +snote(n151,[g,#],4,8:1,1/8,1/16,14.5,14.75,[1])-note(151,[g,#],4,273970,278200,282640,65). +snote(n152,[a,#],3,8:1,1/8,1/16,14.5,14.75,[3])-note(149,[a,#],3,272740,277790,282640,52). +snote(n153,[f,#],4,8:1,3/16,1/16,14.75,15.0,[1])-note(152,[f,#],4,277740,280410,282640,55). +snote(n154,[e,n],4,8:1,3/16,1/16,14.75,15.0,[3])-note(153,[e,n],4,277880,278940,282640,49). +snote(n155,[f,#],3,8:1,3/16,1/16,14.75,15.0,[4])-note(154,[f,#],3,277980,279410,282640,42). +snote(n156,[b,n],4,8:2,0,1/4,15.0,16.0,[1])-note(155,[b,n],4,282610,289660,303680,53). +snote(n157,[d,#],4,8:2,0,1/4,15.0,16.0,[2])-note(156,[d,#],4,282840,287690,287920,38). +snote(n158,[a,n],3,8:2,0,1/16,15.0,15.25,[3])-note(158,[a,n],3,282950,286630,287920,32). +snote(n159,[b,n],2,8:2,0,1/4,15.0,16.0,[7])-note(157,[b,n],2,282910,293370,303680,30). +snote(n160,[b,n],3,8:2,1/16,1/16,15.25,15.5,[3])-note(159,[b,n],3,287990,290090,303680,37). +snote(n161,[f,#],3,8:2,1/16,1/8,15.25,15.75,[4])-note(160,[f,#],3,288150,292690,303680,30). +snote(n162,[a,n],3,8:2,1/8,1/16,15.5,15.75,[3])-note(161,[a,n],3,291830,292750,303680,46). +snote(n163,[b,n],3,8:2,3/16,1/16,15.75,16.0,[3])-note(162,[b,n],3,296380,297650,303680,44). +snote(n164,[f,#],3,8:2,3/16,1/16,15.75,16.0,[4])-note(163,[f,#],3,296790,297670,303680,12). +snote(n165,[g,#],4,9:1,0,1/8,16.0,16.5,[1])-note(165,[g,#],4,302750,311510,320960,45). +snote(n166,[e,n],4,9:1,0,1/8,16.0,16.5,[2])-note(167,[e,n],4,303110,310790,320960,38). +snote(n167,[g,#],3,9:1,0,1/16,16.0,16.25,[3])-note(166,[g,#],3,303030,308370,308370,26). +snote(n168,[e,n],2,9:1,0,1/4,16.0,17.0,[7])-note(164,[e,n],2,302750,315600,320960,25). +snote(n169,[b,n],3,9:1,1/16,1/16,16.25,16.5,[3])-note(169,[b,n],3,308270,312220,320960,35). +snote(n170,[b,n],2,9:1,1/16,1/8,16.25,16.75,[4])-note(168,[b,n],2,308170,311530,320960,24). +snote(n171,[d,#],4,9:1,1/8,1/16,16.5,16.75,[1])-note(170,[d,#],4,312000,315750,320960,48). +snote(n172,[g,#],3,9:1,1/8,1/16,16.5,16.75,[3])-note(171,[g,#],3,312360,314740,320960,30). +snote(n173,[e,n],4,9:1,3/16,1/16,16.75,17.0,[1])-note(172,[e,n],4,315690,319860,320960,53). +snote(n174,[b,n],3,9:1,3/16,1/16,16.75,17.0,[3])-note(174,[b,n],3,316020,320320,320960,33). +snote(n175,[b,n],2,9:1,3/16,1/16,16.75,17.0,[4])-note(173,[b,n],2,315750,319890,320960,33). +snote(n176,[f,#],4,9:2,0,5/16,17.0,18.25,[1])-note(175,[f,#],4,319980,337300,352400,47). +snote(n177,[d,#],4,9:2,0,3/8,17.0,18.5,[2])-note(177,[d,#],4,320220,340720,352400,37). +snote(n178,[a,n],3,9:2,0,1/16,17.0,17.25,[3])-note(178,[a,n],3,320270,324990,324990,33). +snote(n179,[b,n],1,9:2,0,1/4,17.0,18.0,[7])-note(176,[b,n],1,320020,331090,335520,30). +snote(n180,[b,n],3,9:2,1/16,1/16,17.25,17.5,[3])-note(180,[b,n],3,324970,328860,335520,37). +snote(n181,[b,n],2,9:2,1/16,1/8,17.25,17.75,[4])-note(179,[b,n],2,324830,328830,335520,33). +snote(n182,[a,n],3,9:2,1/8,1/16,17.5,17.75,[3])-note(181,[a,n],3,328450,332220,335520,36). +snote(n183,[b,n],3,9:2,3/16,1/16,17.75,18.0,[3])-note(182,[b,n],3,332200,335920,335920,40). +snote(n184,[b,n],2,9:2,3/16,1/16,17.75,18.0,[4])-note(183,[b,n],2,332310,335740,335740,27). +snote(n185,[a,n],3,10:1,0,1/16,18.0,18.25,[3])-note(184,[a,n],3,335880,339910,352400,38). +snote(n186,[b,n],1,10:1,0,1/4,18.0,19.0,[7])-note(185,[b,n],1,336030,349380,352400,25). +snote(n187,[g,#],4,10:1,1/16,1/16,18.25,18.5,[1])-note(186,[g,#],4,339850,342070,352400,51). +snote(n188,[b,n],3,10:1,1/16,1/16,18.25,18.5,[3])-note(187,[b,n],3,339860,341790,352400,44). +snote(n189,[b,n],2,10:1,1/16,1/8,18.25,18.75,[4])-note(188,[b,n],2,339970,342970,352400,36). +snote(n190,[g,#],4,10:1,1/8,1/16,18.5,18.75,[1])-note(189,[g,#],4,343540,346420,352400,56). +snote(n191,[d,#],4,10:1,1/8,1/8,18.5,19.0,[2])-note(190,[d,#],4,343630,347830,352400,45). +snote(n192,[a,n],3,10:1,1/8,1/16,18.5,18.75,[3])-note(191,[a,n],3,343850,345830,352400,30). +snote(n193,[f,#],4,10:1,3/16,1/16,18.75,19.0,[1])-note(192,[f,#],4,347170,348880,352400,55). +snote(n194,[b,n],3,10:1,3/16,1/16,18.75,19.0,[3])-note(194,[b,n],3,347480,347890,352400,37). +snote(n195,[b,n],2,10:1,3/16,1/16,18.75,19.0,[4])-note(193,[b,n],2,347360,350150,352400,32). +snote(n196,[g,#],4,10:2,0,5/16,19.0,20.25,[1])-note(195,[g,#],4,351770,369660,384000,52). +snote(n197,[e,n],4,10:2,0,1/4,19.0,20.0,[2])-note(197,[e,n],4,352010,368560,368560,43). +snote(n198,[g,#],3,10:2,0,1/16,19.0,19.25,[3])-note(198,[g,#],3,352150,356980,356980,24). +snote(n199,[e,n],2,10:2,0,1/4,19.0,20.0,[7])-note(196,[e,n],2,351950,364240,367440,24). +snote(n200,[b,n],3,10:2,1/16,1/16,19.25,19.5,[3])-note(200,[b,n],3,356960,361320,367440,35). +snote(n201,[b,n],2,10:2,1/16,1/8,19.25,19.75,[4])-note(199,[b,n],2,356860,360570,367440,24). +snote(n202,[g,#],3,10:2,1/8,1/16,19.5,19.75,[3])-note(201,[g,#],3,360550,363900,367440,32). +snote(n203,[b,n],3,10:2,3/16,1/16,19.75,20.0,[3])-note(203,[b,n],3,364130,367640,367640,39). +snote(n204,[b,n],2,10:2,3/16,1/16,19.75,20.0,[4])-note(202,[b,n],2,364100,367700,367700,28). +snote(n205,[g,#],3,11:1,0,1/16,20.0,20.25,[3])-note(204,[g,#],3,367850,370540,384000,43). +snote(n206,[e,n],2,11:1,0,1/4,20.0,21.0,[7])-note(205,[e,n],2,367870,379290,384000,28). +snote(n207,[a,n],4,11:1,1/16,1/16,20.25,20.5,[1])-note(206,[a,n],4,371480,373510,384000,54). +snote(n208,[e,n],4,11:1,1/16,1/16,20.25,20.5,[3])-note(208,[e,n],4,371860,373290,384000,41). +snote(n209,[b,n],2,11:1,1/16,1/8,20.25,20.75,[4])-note(207,[b,n],2,371800,373960,384000,31). +snote(n210,[a,n],4,11:1,1/8,1/16,20.5,20.75,[1])-note(209,[a,n],4,375170,379120,384000,65). +snote(n211,[b,n],3,11:1,1/8,1/16,20.5,20.75,[3])-note(210,[b,n],3,375650,377960,384000,39). +snote(n212,[g,#],4,11:1,3/16,1/16,20.75,21.0,[1])-note(211,[g,#],4,378760,380320,384000,67). +snote(n213,[e,n],4,11:1,3/16,1/16,20.75,21.0,[3])-note(213,[e,n],4,379150,379470,384000,35). +snote(n214,[b,n],2,11:1,3/16,1/16,20.75,21.0,[4])-note(212,[b,n],2,379070,382480,384000,35). +snote(n215,[c,#],5,11:2,0,3/16,21.0,21.75,[1])-note(214,[c,#],5,383250,389340,400240,71). +snote(n216,[d,#],4,11:2,0,1/16,21.0,21.25,[3])-note(216,[d,#],4,383540,389220,400240,47). +snote(n217,[b,n],1,11:2,0,1/4,21.0,22.0,[7])-note(215,[b,n],1,383500,397030,400240,34). +snote(n218,[a,n],4,11:2,1/16,1/16,21.25,21.5,[3])-note(218,[a,n],4,387970,389590,400240,59). +snote(n219,[b,n],2,11:2,1/16,1/8,21.25,21.75,[4])-note(217,[b,n],2,387860,391610,400240,33). +snote(n220,[b,n],3,11:2,1/8,1/16,21.5,21.75,[3])-note(219,[b,n],3,391570,393950,400240,55). +snote(n221,[b,n],4,11:2,3/16,1/16,21.75,22.0,[1])-note(220,[b,n],4,395060,396060,400240,63). +snote(n222,[d,#],4,11:2,3/16,1/16,21.75,22.0,[3])-note(222,[d,#],4,395320,395910,400240,40). +snote(n223,[b,n],2,11:2,3/16,1/16,21.75,22.0,[4])-note(221,[b,n],2,395200,398730,400240,37). +snote(n224,[a,n],4,12:1,0,1/16,22.0,22.25,[1])-note(223,[a,n],4,399130,403990,415280,56). +snote(n225,[b,n],3,12:1,0,1/16,22.0,22.25,[3])-note(225,[b,n],3,399500,402590,402590,39). +snote(n226,[e,n],2,12:1,0,1/4,22.0,23.0,[7])-note(224,[e,n],2,399180,411860,415280,32). +snote(n227,[g,#],4,12:1,1/16,1/16,22.25,22.5,[1])-note(226,[g,#],4,403070,405340,415280,50). +snote(n228,[e,n],4,12:1,1/16,1/16,22.25,22.5,[3])-note(228,[e,n],4,403220,404220,415280,38). +snote(n229,[b,n],2,12:1,1/16,1/8,22.25,22.75,[4])-note(227,[b,n],2,403180,406490,415280,20). +snote(n230,[d,#],4,12:1,1/8,1/16,22.5,22.75,[1])-note(229,[d,#],4,406660,409660,415280,49). +snote(n231,[g,#],3,12:1,1/8,1/16,22.5,22.75,[3])-note(230,[g,#],3,407010,409020,415280,37). +snote(n232,[e,n],4,12:1,3/16,1/16,22.75,23.0,[1])-note(232,[e,n],4,410460,415280,415280,58). +snote(n233,[b,n],3,12:1,3/16,1/16,22.75,23.0,[3])-note(233,[b,n],3,410630,410990,415280,36). +snote(n234,[b,n],2,12:1,3/16,1/16,22.75,23.0,[4])-note(231,[b,n],2,410450,413500,415280,32). +snote(n235,[f,#],4,12:2,0,5/16,23.0,24.25,[1])-note(234,[f,#],4,415140,432370,434960,55). +snote(n236,[c,#],4,12:2,0,1/16,23.0,23.25,[2])-note(237,[c,#],4,415610,419990,420000,32). +snote(n237,[a,n],3,12:2,0,1/16,23.0,23.25,[3])-note(236,[a,n],3,415510,417610,417610,31). +snote(n238,[b,n],1,12:2,0,1/4,23.0,24.0,[7])-note(235,[b,n],1,415510,427820,430480,26). +snote(n239,[d,#],4,12:2,1/16,1/16,23.25,23.5,[2])-note(238,[d,#],4,419940,423640,430480,40). +snote(n240,[b,n],3,12:2,1/16,1/16,23.25,23.5,[3])-note(240,[b,n],3,420340,421670,421670,29). +snote(n241,[b,n],2,12:2,1/16,1/8,23.25,23.75,[4])-note(239,[b,n],2,420200,424390,430480,27). +snote(n242,[c,#],4,12:2,1/8,1/16,23.5,23.75,[2])-note(241,[c,#],4,423600,427480,430480,44). +snote(n243,[a,n],3,12:2,1/8,1/16,23.5,23.75,[3])-note(242,[a,n],3,423890,424800,430480,34). +snote(n244,[d,#],4,12:2,3/16,1/16,23.75,24.0,[2])-note(243,[d,#],4,427410,431500,431500,49). +snote(n245,[b,n],3,12:2,3/16,1/16,23.75,24.0,[3])-note(245,[b,n],3,427740,428760,430480,24). +snote(n246,[b,n],2,12:2,3/16,1/16,23.75,24.0,[4])-note(244,[b,n],2,427430,431060,431060,34). +snote(n247,[c,#],4,13:1,0,1/16,24.0,24.25,[2])-note(246,[c,#],4,431030,435080,435080,49). +snote(n248,[a,n],3,13:1,0,1/16,24.0,24.25,[3])-note(248,[a,n],3,431150,432820,434960,40). +snote(n249,[b,n],1,13:1,0,1/4,24.0,25.0,[7])-note(247,[b,n],1,431110,443880,447360,28). +snote(n250,[g,#],4,13:1,1/16,1/16,24.25,24.5,[1])-note(249,[g,#],4,434940,437570,447360,62). +snote(n251,[d,#],4,13:1,1/16,1/16,24.25,24.5,[2])-note(252,[d,#],4,435310,437110,447360,30). +snote(n252,[b,n],3,13:1,1/16,1/16,24.25,24.5,[3])-note(251,[b,n],3,435270,436450,436450,34). +snote(n253,[b,n],2,13:1,1/16,1/8,24.25,24.75,[4])-note(250,[b,n],2,435220,438320,447360,31). +snote(n254,[g,#],4,13:1,1/8,1/16,24.5,24.75,[1])-note(253,[g,#],4,438560,441880,447360,63). +snote(n255,[c,#],4,13:1,1/8,1/16,24.5,24.75,[2])-note(254,[c,#],4,438850,441160,447360,45). +snote(n256,[a,n],3,13:1,1/8,1/16,24.5,24.75,[3])-note(255,[a,n],3,438970,439590,447360,22). +snote(n257,[f,#],4,13:1,3/16,1/16,24.75,25.0,[1])-note(256,[f,#],4,442240,443790,447360,59). +snote(n258,[d,#],4,13:1,3/16,1/16,24.75,25.0,[2])-note(259,[d,#],4,442440,443200,447360,44). +snote(n259,[b,n],3,13:1,3/16,1/16,24.75,25.0,[3])-note(258,[b,n],3,442420,442900,447360,45). +snote(n260,[b,n],2,13:1,3/16,1/16,24.75,25.0,[4])-note(257,[b,n],2,442380,445390,447360,39). +snote(n261,[e,n],4,13:2,0,1/4,25.0,26.0,[1])-note(260,[e,n],4,446780,459350,463280,59). +snote(n262,[g,#],3,13:2,0,1/16,25.0,25.25,[3])-note(262,[g,#],3,447280,451210,451210,29). +snote(n263,[e,n],2,13:2,0,1/4,25.0,26.0,[7])-note(261,[e,n],2,447140,456720,463280,26). +snote(n264,[b,n],3,13:2,1/16,1/16,25.25,25.5,[3])-note(263,[b,n],3,450860,454770,463280,40). +snote(n265,[b,n],2,13:2,1/16,1/8,25.25,25.75,[4])-note(264,[b,n],2,450870,453570,463280,24). +snote(n266,[g,#],3,13:2,1/8,1/16,25.5,25.75,[3])-note(265,[g,#],3,454420,455480,463280,38). +snote(n267,[b,n],3,13:2,3/16,1/16,25.75,26.0,[3])-note(266,[b,n],3,458520,459050,463280,31). +snote(n268,[b,n],2,13:2,3/16,1/16,25.75,26.0,[4])-note(267,[b,n],2,458590,459390,463280,27). +snote(n269,[b,n],4,14:1,0,1/16,26.0,26.25,[1])-note(268,[b,n],4,463190,465830,478400,53). +snote(n270,[f,#],4,14:1,0,1/16,26.0,26.25,[2])-note(270,[f,#],4,463410,466730,478400,31). +snote(n271,[d,n],4,14:1,0,1/16,26.0,26.25,[3])-note(271,[d,n],4,463580,465880,478400,31). +snote(n272,[e,n],2,14:1,0,1/4,26.0,27.0,[7])-note(269,[e,n],2,463350,476010,478400,31). +snote(n273,[c,#],5,14:1,1/16,1/16,26.25,26.5,[1])-note(272,[c,#],5,467690,469920,478400,56). +snote(n274,[g,#],4,14:1,1/16,1/16,26.25,26.5,[2])-note(274,[g,#],4,467920,469900,478400,43). +snote(n275,[e,n],4,14:1,1/16,1/16,26.25,26.5,[3])-note(275,[e,n],4,468100,468990,478400,38). +snote(n276,[e,n],3,14:1,1/16,1/8,26.25,26.75,[4])-note(273,[e,n],3,467810,470520,478400,34). +snote(n277,[c,#],5,14:1,1/8,1/16,26.5,26.75,[1])-note(276,[c,#],5,471080,473310,478400,63). +snote(n278,[f,#],4,14:1,1/8,1/16,26.5,26.75,[2])-note(277,[f,#],4,471310,473710,478400,47). +snote(n279,[d,n],4,14:1,1/8,1/16,26.5,26.75,[3])-note(278,[d,n],4,471340,472280,478400,44). +snote(n280,[b,n],4,14:1,3/16,1/16,26.75,27.0,[1])-note(279,[b,n],4,474630,475960,478400,64). +snote(n281,[g,#],4,14:1,3/16,1/16,26.75,27.0,[2])-note(281,[g,#],4,474820,476290,478400,46). +snote(n282,[e,n],4,14:1,3/16,1/16,26.75,27.0,[3])-note(282,[e,n],4,474850,475130,478400,37). +snote(n283,[e,n],3,14:1,3/16,1/16,26.75,27.0,[4])-note(280,[e,n],3,474710,477230,478400,42). +snote(n284,[a,n],4,14:2,0,1/16,27.0,27.25,[1])-note(283,[a,n],4,478030,481940,491520,62). +snote(n285,[c,#],4,14:2,0,1/16,27.0,27.25,[3])-note(285,[c,#],4,478280,481560,491520,44). +snote(n286,[a,n],2,14:2,0,1/4,27.0,28.0,[7])-note(284,[a,n],2,478150,487490,491520,39). +snote(n287,[b,n],4,14:2,1/16,1/16,27.25,27.5,[1])-note(287,[b,n],4,481540,484830,491520,67). +snote(n288,[e,n],4,14:2,1/16,1/16,27.25,27.5,[3])-note(288,[e,n],4,481680,484470,491520,54). +snote(n289,[e,n],3,14:2,1/16,1/8,27.25,27.75,[4])-note(286,[e,n],3,481470,484090,491520,39). +snote(n290,[g,#],4,14:2,1/8,1/16,27.5,27.75,[1])-note(289,[g,#],4,484610,488020,491520,65). +snote(n291,[c,#],4,14:2,1/8,1/16,27.5,27.75,[3])-note(290,[c,#],4,484810,485690,491520,56). +snote(n292,[a,n],4,14:2,3/16,1/16,27.75,28.0,[1])-note(291,[a,n],4,487970,488910,491520,69). +snote(n293,[e,n],4,14:2,3/16,1/16,27.75,28.0,[3])-note(293,[e,n],4,488090,488420,491520,56). +snote(n294,[e,n],3,14:2,3/16,1/16,27.75,28.0,[4])-note(292,[e,n],3,487990,488850,491520,48). +snote(n295,[d,#],5,15:1,0,1/16,28.0,28.25,[1])-note(294,[d,#],5,491370,495960,505840,73). +snote(n296,[b,#],4,15:1,0,1/16,28.0,28.25,[2])-note(296,[b,#],4,491500,495660,505840,62). +snote(n297,[f,#],4,15:1,0,1/16,28.0,28.25,[3])-note(297,[f,#],4,491550,493450,505840,55). +snote(n298,[g,#],2,15:1,0,1/4,28.0,29.0,[7])-note(295,[g,#],2,491480,506040,506040,55). +snote(n299,[e,n],5,15:1,1/16,1/16,28.25,28.5,[1])-note(298,[e,n],5,495490,497460,505840,74). +snote(n300,[g,#],4,15:1,1/16,1/16,28.25,28.5,[3])-note(300,[g,#],4,495700,497400,505840,52). +snote(n301,[g,#],3,15:1,1/16,1/8,28.25,28.75,[4])-note(299,[g,#],3,495600,498690,505840,44). +snote(n302,[e,n],5,15:1,1/8,1/16,28.5,28.75,[1])-note(301,[e,n],5,498750,501510,505840,71). +snote(n303,[a,#],4,15:1,1/8,1/16,28.5,28.75,[2])-note(302,[a,#],4,498860,499810,505840,60). +snote(n304,[f,#],4,15:1,1/8,1/16,28.5,28.75,[3])-note(303,[f,#],4,498980,500290,505840,52). +snote(n305,[d,#],5,15:1,3/16,1/16,28.75,29.0,[1])-note(304,[d,#],5,501960,502920,505840,74). +snote(n306,[b,#],4,15:1,3/16,1/16,28.75,29.0,[2])-note(307,[b,#],4,502120,502380,505840,51). +snote(n307,[g,#],4,15:1,3/16,1/16,28.75,29.0,[3])-note(305,[g,#],4,502080,502730,505840,59). +snote(n308,[g,#],3,15:1,3/16,1/16,28.75,29.0,[4])-note(306,[g,#],3,502110,504240,505840,52). +snote(n309,[c,#],5,15:2,0,1/16,29.0,29.25,[1])-note(309,[c,#],5,505410,509170,519840,75). +snote(n310,[e,n],4,15:2,0,1/16,29.0,29.25,[3])-note(310,[e,n],4,505500,509030,519840,62). +snote(n311,[c,#],3,15:2,0,1/4,29.0,30.0,[7])-note(308,[c,#],3,505390,516460,519840,52). +snote(n312,[d,#],5,15:2,1/16,1/16,29.25,29.5,[1])-note(311,[d,#],5,508820,511110,519840,76). +snote(n313,[g,#],4,15:2,1/16,1/16,29.25,29.5,[3])-note(313,[g,#],4,508990,511940,519840,56). +snote(n314,[g,#],3,15:2,1/16,1/8,29.25,29.75,[4])-note(312,[g,#],3,508840,511820,519840,49). +snote(n315,[c,n],5,15:2,1/8,1/16,29.5,29.75,[1])-note(314,[c,n],5,512030,516370,519840,72). +snote(n316,[e,n],4,15:2,1/8,1/16,29.5,29.75,[3])-note(315,[e,n],4,512200,514620,519840,60). +snote(n317,[c,#],5,15:2,3/16,1/16,29.75,30.0,[1])-note(316,[c,#],5,515590,516720,519840,75). +snote(n318,[g,#],4,15:2,3/16,1/16,29.75,30.0,[3])-note(318,[g,#],4,515650,516190,519840,61). +snote(n319,[g,#],3,15:2,3/16,1/16,29.75,30.0,[4])-note(317,[g,#],3,515610,516670,519840,57). +snote(n320,[e,n],5,16:1,0,1/16,30.0,30.25,[1])-note(319,[e,n],5,519870,522340,534400,80). +snote(n321,[e,n],4,16:1,0,1/16,30.0,30.25,[2])-note(322,[e,n],4,520030,521750,534400,61). +snote(n322,[a,#],4,16:1,0,1/16,30.0,30.25,[3])-note(321,[a,#],4,520020,522790,534400,58). +snote(n323,[a,#],3,16:1,0,1/16,30.0,30.25,[4])-note(320,[a,#],3,519980,522610,534400,45). +snote(n324,[f,#],3,16:1,0,1/16,30.0,30.25,[5])-note(325,[f,#],3,520140,522870,534400,39). +snote(n325,[e,n],3,16:1,0,1/16,30.0,30.25,[6])-note(324,[e,n],3,520130,522730,534400,40). +snote(n326,[c,#],3,16:1,0,1/16,30.0,30.25,[7])-note(323,[c,#],3,520080,522730,534400,43). +snote(n327,[f,#],5,16:1,1/16,1/16,30.25,30.5,[1])-note(326,[f,#],5,524190,525480,534400,78). +snote(n328,[a,#],4,16:1,1/16,1/16,30.25,30.5,[2])-note(332,[a,#],4,524470,526220,534400,57). +snote(n329,[f,#],4,16:1,1/16,1/16,30.25,30.5,[3])-note(328,[f,#],4,524270,525680,534400,69). +snote(n330,[a,#],3,16:1,1/16,1/16,30.25,30.5,[4])-note(327,[a,#],3,524240,525730,534400,58). +snote(n331,[f,#],3,16:1,1/16,1/16,30.25,30.5,[5])-note(329,[f,#],3,524370,526030,534400,45). +snote(n332,[e,n],3,16:1,1/16,1/16,30.25,30.5,[6])-note(330,[e,n],3,524400,526060,534400,42). +snote(n333,[c,#],3,16:1,1/16,1/16,30.25,30.5,[7])-note(331,[c,#],3,524410,525630,534400,44). +snote(n334,[d,#],5,16:1,1/8,1/16,30.5,30.75,[1])-note(334,[d,#],5,527630,529870,534400,83). +snote(n335,[a,#],4,16:1,1/8,1/16,30.5,30.75,[2])-note(339,[a,#],4,527890,528240,534400,50). +snote(n336,[d,#],4,16:1,1/8,1/16,30.5,30.75,[3])-note(335,[d,#],4,527710,528890,534400,80). +snote(n337,[a,#],3,16:1,1/8,1/16,30.5,30.75,[4])-note(333,[a,#],3,527620,528970,534400,68). +snote(n338,[f,#],3,16:1,1/8,1/16,30.5,30.75,[5])-note(337,[f,#],3,527780,529190,534400,51). +snote(n339,[e,n],3,16:1,1/8,1/16,30.5,30.75,[6])-note(338,[e,n],3,527840,529460,534400,44). +snote(n340,[c,#],3,16:1,1/8,1/16,30.5,30.75,[7])-note(336,[c,#],3,527720,529600,534400,58). +snote(n341,[e,n],5,16:1,3/16,1/16,30.75,31.0,[1])-note(341,[e,n],5,530940,532040,534400,87). +snote(n342,[a,#],4,16:1,3/16,1/16,30.75,31.0,[2])-note(344,[a,#],4,530990,532200,534400,61). +snote(n343,[e,n],4,16:1,3/16,1/16,30.75,31.0,[3])-note(343,[e,n],4,530960,531570,534400,77). +snote(n344,[a,#],3,16:1,3/16,1/16,30.75,31.0,[4])-note(340,[a,#],3,530890,532250,534400,67). +snote(n345,[f,#],3,16:1,3/16,1/16,30.75,31.0,[5])-note(345,[f,#],3,531060,532350,534400,53). +snote(n346,[e,n],3,16:1,3/16,1/16,30.75,31.0,[6])-note(346,[e,n],3,531180,533570,534400,42). +snote(n347,[c,#],3,16:1,3/16,1/16,30.75,31.0,[7])-note(342,[c,#],3,530960,532300,534400,55). +snote(n348,[f,#],5,16:2,0,1/16,31.0,31.25,[1])-note(347,[f,#],5,534540,536600,541440,85). +snote(n349,[f,#],4,16:2,0,1/16,31.0,31.25,[2])-note(350,[f,#],4,534600,536410,541440,75). +snote(n350,[a,#],4,16:2,0,1/16,31.0,31.25,[3])-note(351,[a,#],4,534620,540680,541440,73). +snote(n351,[a,#],3,16:2,0,1/16,31.0,31.25,[4])-note(348,[a,#],3,534550,536830,541440,67). +snote(n352,[f,#],3,16:2,0,1/16,31.0,31.25,[5])-note(349,[f,#],3,534600,539640,541440,57). +snote(n353,[e,n],3,16:2,0,1/16,31.0,31.25,[6])-note(353,[e,n],3,534720,536980,541440,57). +snote(n354,[c,n],3,16:2,0,1/16,31.0,31.25,[7])-note(352,[c,n],3,534670,536880,541440,61). +snote(n355,[g,#],5,16:2,1/16,1/16,31.25,31.5,[1])-note(354,[g,#],5,538260,539290,541440,87). +snote(n356,[a,#],4,16:2,1/16,1/16,31.25,31.5,[2])-deletion. +snote(n357,[g,#],4,16:2,1/16,1/16,31.25,31.5,[3])-note(358,[g,#],4,538380,539390,541440,73). +snote(n358,[a,#],3,16:2,1/16,1/16,31.25,31.5,[4])-note(355,[a,#],3,538270,539300,541440,68). +snote(n359,[f,#],3,16:2,1/16,1/16,31.25,31.5,[5])-deletion. +snote(n360,[e,n],3,16:2,1/16,1/16,31.25,31.5,[6])-note(357,[e,n],3,538360,539860,541440,56). +snote(n361,[c,n],3,16:2,1/16,1/16,31.25,31.5,[7])-note(356,[c,n],3,538350,539110,541440,58). +snote(n362,[e,n],5,16:2,1/8,1/16,31.5,31.75,[1])-note(359,[e,n],5,541670,543030,545760,87). +snote(n363,[a,#],4,16:2,1/8,1/16,31.5,31.75,[2])-note(364,[a,#],4,541810,543300,545760,66). +snote(n364,[e,n],4,16:2,1/8,1/16,31.5,31.75,[3])-note(361,[e,n],4,541720,542640,545760,82). +snote(n365,[a,#],3,16:2,1/8,1/16,31.5,31.75,[4])-note(360,[a,#],3,541690,542980,545760,77). +snote(n366,[c,n],3,16:2,1/8,1/16,31.5,31.75,[5])-note(362,[c,n],3,541800,543030,545760,65). +snote(n367,[f,#],3,16:2,1/8,1/16,31.5,31.75,[6])-note(363,[f,#],3,541800,543350,545760,65). +snote(n368,[e,n],3,16:2,1/8,1/16,31.5,31.75,[7])-note(365,[e,n],3,541890,543490,545760,57). +snote(n369,[f,#],5,16:2,3/16,1/16,31.75,32.0,[1])-note(367,[f,#],5,545860,547830,553280,87). +snote(n370,[a,#],4,16:2,3/16,1/16,31.75,32.0,[2])-note(369,[a,#],4,545900,548120,553280,78). +snote(n371,[f,#],4,16:2,3/16,1/16,31.75,32.0,[3])-note(368,[f,#],4,545890,547630,553280,82). +snote(n372,[a,#],3,16:2,3/16,1/16,31.75,32.0,[4])-note(366,[a,#],3,545860,547790,553280,75). +snote(n373,[f,#],3,16:2,3/16,1/16,31.75,32.0,[5])-note(370,[f,#],3,545920,548050,553280,66). +snote(n374,[e,n],3,16:2,3/16,1/16,31.75,32.0,[6])-note(372,[e,n],3,546130,548300,553280,50). +snote(n375,[c,n],3,16:2,3/16,1/16,31.75,32.0,[7])-note(371,[c,n],3,545950,547700,553280,66). +snote(n376,[g,#],5,17:1,0,5/16,32.0,33.25,[1])-note(373,[g,#],5,552190,570300,577840,87). +snote(n377,[e,n],5,17:1,0,1/4,32.0,33.0,[2])-note(378,[e,n],5,552330,571070,577840,69). +snote(n378,[g,#],4,17:1,0,1/16,32.0,32.25,[3])-note(374,[g,#],4,552280,558460,558460,71). +snote(n379,[b,n],3,17:1,0,1/2,32.0,34.0,[4])-note(375,[b,n],3,552300,583580,588160,63). +snote(n380,[g,#],3,17:1,0,1/2,32.0,34.0,[5])-note(377,[g,#],3,552330,583800,588160,56). +snote(n381,[e,n],3,17:1,0,1/2,32.0,34.0,[6])-note(379,[e,n],3,552420,584470,588160,50). +snote(n382,[b,n],2,17:1,0,1/2,32.0,34.0,[7])-note(376,[b,n],2,552330,584020,588160,59). +snote(n383,[b,n],4,17:1,1/16,1/16,32.25,32.5,[3])-note(380,[b,n],4,558320,563200,577840,62). +snote(n384,[g,#],4,17:1,1/8,1/16,32.5,32.75,[3])-note(381,[g,#],4,562690,566540,577840,56). +snote(n385,[b,n],4,17:1,3/16,1/16,32.75,33.0,[3])-note(382,[b,n],4,566410,570570,577840,60). +snote(n386,[g,#],4,17:2,0,1/16,33.0,33.25,[3])-note(383,[g,#],4,569890,571670,577840,56). +snote(n387,[f,#],5,17:2,1/16,1/16,33.25,33.5,[1])-note(384,[f,#],5,573630,575600,577840,67). +snote(n388,[b,n],4,17:2,1/16,1/16,33.25,33.5,[3])-note(385,[b,n],4,573880,576850,577840,48). +snote(n389,[e,n],5,17:2,1/8,1/16,33.5,33.75,[1])-note(386,[e,n],5,577600,581790,581790,66). +snote(n390,[g,#],4,17:2,1/8,1/16,33.5,33.75,[3])-note(387,[g,#],4,577930,580600,581680,40). +snote(n391,[c,#],5,17:2,3/16,1/16,33.75,34.0,[1])-note(388,[c,#],5,581900,584080,588160,53). +snote(n392,[b,n],4,17:2,3/16,1/16,33.75,34.0,[3])-note(389,[b,n],4,581970,583060,588160,48). +snote(n393,[d,#],5,18:1,0,1/4,34.0,35.0,[1])-note(390,[d,#],5,587360,600920,604400,59). +snote(n394,[d,#],4,18:1,0,1/16,34.0,34.25,[3])-note(391,[d,#],4,587670,592590,592590,45). +snote(n395,[f,#],3,18:1,0,1/16,34.0,34.25,[7])-note(392,[f,#],3,587790,601180,604400,25). +snote(n396,[f,#],4,18:1,1/16,1/16,34.25,34.5,[3])-note(393,[f,#],4,592710,597610,604400,39). +snote(n397,[b,n],3,18:1,1/16,1/16,34.25,34.5,[4])-note(394,[b,n],3,592880,596330,604400,28). +snote(n398,[d,#],4,18:1,1/8,1/16,34.5,34.75,[3])-note(395,[d,#],4,596710,600240,604400,36). +snote(n399,[b,n],2,18:1,1/8,1/16,34.5,34.75,[7])-note(396,[b,n],2,596750,600210,604400,13). +snote(n400,[f,#],4,18:1,3/16,1/16,34.75,35.0,[3])-note(397,[f,#],4,600010,601710,604400,46). +snote(n401,[b,n],3,18:1,3/16,1/16,34.75,35.0,[4])-note(398,[b,n],3,600310,603310,604400,38). +snote(n402,[e,n],5,18:2,0,1/16,35.0,35.25,[1])-note(399,[e,n],5,603670,608440,608440,62). +snote(n403,[e,n],4,18:2,0,1/16,35.0,35.25,[3])-note(400,[e,n],4,603870,607870,607870,46). +snote(n404,[c,#],3,18:2,0,1/16,35.0,35.25,[7])-note(401,[c,#],3,603890,608550,608550,35). +snote(n405,[d,#],5,18:2,1/16,1/16,35.25,35.5,[1])-note(402,[d,#],5,607250,611630,611630,66). +snote(n406,[g,#],4,18:2,1/16,1/16,35.25,35.5,[3])-note(403,[g,#],4,607380,611250,611250,50). +snote(n407,[g,#],3,18:2,1/16,1/16,35.25,35.5,[4])-note(404,[g,#],3,607610,611150,611150,32). +snote(n408,[c,#],5,18:2,1/8,1/16,35.5,35.75,[1])-note(405,[c,#],5,610840,615680,620400,62). +snote(n409,[e,n],4,18:2,1/8,1/16,35.5,35.75,[3])-note(406,[e,n],4,610960,613670,614560,44). +snote(n410,[g,#],2,18:2,1/8,1/4,35.5,36.5,[7])-note(407,[g,#],2,611000,617540,620400,35). +snote(n411,[g,#],4,18:2,3/16,1/16,35.75,36.0,[1])-note(408,[g,#],4,614910,616620,620400,52). +snote(n412,[g,#],3,18:2,3/16,1/16,35.75,36.0,[4])-note(409,[g,#],3,614920,617420,620400,33). +snote(n413,[b,n],4,19:1,0,1/4,36.0,37.0,[1])-note(410,[b,n],4,619990,631930,636160,54). +snote(n414,[b,n],3,19:1,0,1/16,36.0,36.25,[3])-note(412,[b,n],3,620360,624530,627760,36). +snote(n415,[d,#],3,19:1,0,1/16,36.0,36.25,[7])-note(411,[d,#],3,620000,633190,636160,31). +snote(n416,[d,#],4,19:1,1/16,1/16,36.25,36.5,[3])-note(413,[d,#],4,624210,628420,628420,40). +snote(n417,[g,#],3,19:1,1/16,1/16,36.25,36.5,[4])-note(414,[g,#],3,624340,627580,627760,29). +snote(n418,[b,n],3,19:1,1/8,1/16,36.5,36.75,[3])-note(416,[b,n],3,627920,631410,636160,36). +snote(n419,[g,#],2,19:1,1/8,1/16,36.5,36.75,[7])-note(415,[g,#],2,627800,631310,636160,30). +snote(n420,[d,#],4,19:1,3/16,1/16,36.75,37.0,[3])-note(417,[d,#],4,631760,632320,636160,43). +snote(n421,[g,#],3,19:1,3/16,1/16,36.75,37.0,[4])-note(418,[g,#],3,631870,632380,636160,7). +snote(n422,[c,#],5,19:2,0,1/16,37.0,37.25,[1])-note(419,[c,#],5,636150,638250,640480,54). +snote(n423,[c,#],4,19:2,0,1/16,37.0,37.25,[3])-note(421,[c,#],4,636510,640610,640610,35). +snote(n424,[a,n],2,19:2,0,1/16,37.0,37.25,[7])-note(420,[a,n],2,636430,640560,640560,30). +snote(n425,[b,n],4,19:2,1/16,1/16,37.25,37.5,[1])-note(423,[b,n],4,640500,644420,644420,48). +snote(n426,[e,n],4,19:2,1/16,1/16,37.25,37.5,[3])-note(424,[e,n],4,640810,643960,644000,31). +snote(n427,[e,n],3,19:2,1/16,1/16,37.25,37.5,[4])-note(422,[e,n],3,640470,644430,644430,37). +snote(n428,[a,n],4,19:2,1/8,1/16,37.5,37.75,[1])-note(425,[a,n],4,644100,648980,653120,48). +snote(n429,[c,#],4,19:2,1/8,1/16,37.5,37.75,[3])-note(427,[c,#],4,644370,647270,647680,23). +snote(n430,[e,n],2,19:2,1/8,1/4,37.5,38.5,[7])-note(426,[e,n],2,644250,649890,653120,32). +snote(n431,[e,n],4,19:2,3/16,1/16,37.75,38.0,[1])-note(428,[e,n],4,647900,649420,653120,49). +snote(n432,[e,n],3,19:2,3/16,1/16,37.75,38.0,[4])-note(429,[e,n],3,647930,650500,653120,32). +snote(n433,[g,#],4,20:1,0,1/2,38.0,40.0,[1])-note(430,[g,#],4,652680,685200,692640,46). +snote(n434,[g,#],3,20:1,0,1/16,38.0,38.25,[3])-note(432,[g,#],3,653160,657670,657670,26). +snote(n435,[b,n],2,20:1,0,1/16,38.0,38.25,[7])-note(431,[b,n],2,652910,663200,663200,23). +snote(n436,[b,n],3,20:1,1/16,1/16,38.25,38.5,[3])-note(434,[b,n],3,657880,662140,662140,30). +snote(n437,[e,n],3,20:1,1/16,1/16,38.25,38.5,[4])-note(433,[e,n],3,657710,661710,662000,19). +snote(n438,[g,#],3,20:1,1/8,1/16,38.5,38.75,[3])-note(436,[g,#],3,661960,665660,669840,15). +snote(n439,[e,n],2,20:1,1/8,1/4,38.5,39.5,[7])-note(435,[e,n],2,661710,670300,670300,23). +snote(n440,[b,n],3,20:1,3/16,1/16,38.75,39.0,[3])-note(438,[b,n],3,665720,670130,670130,34). +snote(n441,[e,n],3,20:1,3/16,1/16,38.75,39.0,[4])-note(437,[e,n],3,665520,670230,670230,31). +snote(n442,[g,#],3,20:2,0,1/16,39.0,39.25,[3])-note(440,[g,#],3,669760,673640,678240,25). +snote(n443,[b,n],2,20:2,0,1/16,39.0,39.25,[7])-note(439,[b,n],2,669610,679190,679190,28). +snote(n444,[b,n],3,20:2,1/16,1/16,39.25,39.5,[3])-note(441,[b,n],3,673620,678870,678870,27). +snote(n445,[e,n],3,20:2,1/16,1/16,39.25,39.5,[4])-note(442,[e,n],3,673980,678130,678240,19). +snote(n446,[g,#],3,20:2,1/8,1/16,39.5,39.75,[3])-note(443,[g,#],3,678140,683800,692640,23). +snote(n447,[e,n],2,20:2,1/8,3/8,39.5,41.0,[7])-note(444,[e,n],2,678170,704670,710320,22). +snote(n448,[b,n],3,20:2,3/16,1/16,39.75,40.0,[3])-note(445,[b,n],3,683550,685070,692640,30). +snote(n449,[b,n],2,20:2,3/16,5/16,39.75,41.0,[6])-note(446,[b,n],2,683590,705800,710320,18). +snote(n450,[g,#],4,21:1,0,0,40.0,40.0,[1,grace])-note(447,[g,#],4,691330,694180,694180,38). +snote(n451,[f,#],4,21:1,0,0,40.0,40.0,[1,grace])-note(450,[f,#],4,693140,695700,695700,44). +snote(n452,[e,n],4,21:1,0,1/4,40.0,41.0,[1])-note(451,[e,n],4,695050,705530,710320,40). +snote(n453,[g,#],3,21:1,0,1/4,40.0,41.0,[3])-note(449,[g,#],3,691800,703570,710320,28). +snote(n454,[e,n],3,21:1,0,1/4,40.0,41.0,[4])-deletion. +sustain(0,11). +sustain(400,11). +sustain(480,11). +sustain(720,11). +sustain(880,11). +sustain(960,11). +sustain(1120,11). +sustain(1200,11). +sustain(1280,11). +sustain(1360,11). +sustain(1440,11). +sustain(1600,11). +sustain(1680,11). +sustain(1840,11). +sustain(1920,11). +sustain(2000,11). +sustain(2080,11). +sustain(2240,11). +sustain(2320,11). +sustain(2400,11). +sustain(2480,11). +sustain(2800,11). +sustain(2880,11). +sustain(3120,11). +sustain(3200,11). +sustain(3280,11). +sustain(3360,11). +sustain(3840,11). +sustain(3920,11). +sustain(4000,11). +sustain(4080,11). +sustain(4160,11). +sustain(4240,11). +sustain(4560,11). +sustain(5040,11). +sustain(5120,11). +sustain(5200,11). +sustain(6400,11). +sustain(6480,11). +sustain(6560,11). +sustain(6640,11). +sustain(6880,11). +sustain(6960,11). +sustain(7120,11). +sustain(7280,11). +sustain(7680,11). +sustain(7760,11). +sustain(7840,11). +sustain(7920,11). +sustain(8080,11). +sustain(8160,11). +sustain(8240,11). +sustain(8320,11). +sustain(8480,11). +sustain(8640,11). +sustain(9040,11). +sustain(9120,11). +sustain(9200,11). +sustain(9280,11). +sustain(9600,11). +sustain(9680,11). +sustain(9760,11). +sustain(9840,11). +sustain(10000,11). +sustain(10080,11). +sustain(10160,11). +sustain(10240,11). +sustain(10560,11). +sustain(10640,11). +sustain(10720,11). +sustain(10800,11). +sustain(11120,11). +sustain(11200,11). +sustain(11280,11). +sustain(11360,11). +sustain(11680,11). +sustain(11760,11). +sustain(11840,11). +sustain(11920,11). +sustain(12000,11). +sustain(12080,11). +sustain(12160,11). +sustain(12240,11). +sustain(12720,11). +sustain(12800,11). +sustain(12880,11). +sustain(12960,11). +sustain(13040,11). +sustain(13120,11). +sustain(13200,11). +sustain(13280,11). +sustain(13360,11). +sustain(13440,11). +sustain(14400,11). +sustain(14480,11). +sustain(14560,11). +sustain(14640,11). +sustain(14720,11). +sustain(14800,11). +sustain(14880,11). +sustain(14960,11). +sustain(15040,11). +sustain(15120,11). +sustain(15360,11). +sustain(15440,11). +sustain(15920,11). +sustain(16000,11). +sustain(16080,11). +sustain(16160,11). +sustain(16240,11). +sustain(16320,11). +sustain(16640,11). +sustain(16720,11). +sustain(16800,11). +sustain(16880,11). +sustain(17120,11). +sustain(17200,11). +sustain(17840,11). +sustain(17920,11). +sustain(18000,11). +sustain(18080,11). +sustain(18160,11). +sustain(18240,11). +sustain(18320,11). +sustain(18400,11). +sustain(18480,11). +sustain(18560,11). +sustain(18640,11). +sustain(18720,11). +sustain(19040,11). +sustain(19200,11). +sustain(19600,11). +sustain(19680,11). +sustain(19840,11). +sustain(19920,11). +sustain(20000,11). +sustain(20080,11). +sustain(20240,11). +sustain(20320,11). +sustain(20960,11). +sustain(21040,11). +sustain(21200,11). +sustain(21280,11). +sustain(21440,11). +sustain(21520,11). +sustain(21680,11). +sustain(21760,11). +sustain(21920,11). +sustain(22000,11). +sustain(22080,11). +sustain(22160,11). +sustain(22320,11). +sustain(22400,11). +sustain(22560,11). +sustain(22640,11). +sustain(22720,11). +sustain(22800,11). +sustain(22960,11). +sustain(23040,11). +sustain(23120,11). +sustain(23200,11). +sustain(23360,11). +sustain(23440,11). +sustain(23520,11). +sustain(23600,11). +sustain(23760,11). +sustain(23840,11). +sustain(23920,11). +sustain(24000,11). +sustain(24320,11). +sustain(24400,11). +sustain(24640,11). +sustain(24720,11). +sustain(24880,11). +sustain(24960,11). +sustain(25040,11). +sustain(25120,11). +sustain(25200,11). +sustain(25280,11). +sustain(25600,11). +sustain(25680,11). +sustain(25760,11). +sustain(25840,11). +sustain(25920,11). +sustain(26000,11). +sustain(26080,11). +sustain(26160,11). +sustain(26240,11). +sustain(26320,11). +sustain(27520,11). +sustain(27600,11). +sustain(27680,11). +sustain(27760,11). +sustain(28240,11). +sustain(28320,11). +sustain(28400,11). +sustain(28480,11). +sustain(28960,11). +sustain(29040,11). +sustain(29200,11). +sustain(29280,11). +sustain(29360,11). +sustain(29440,11). +sustain(29760,11). +sustain(29840,11). +sustain(29920,11). +sustain(30000,11). +sustain(30160,11). +sustain(30240,11). +sustain(30400,11). +sustain(30480,11). +sustain(30720,11). +sustain(30800,11). +sustain(30880,11). +sustain(30960,11). +sustain(31120,11). +sustain(31200,11). +sustain(31280,11). +sustain(31360,11). +sustain(31440,11). +sustain(31520,11). +sustain(31680,11). +sustain(31760,11). +sustain(31840,11). +sustain(31920,11). +sustain(32080,11). +sustain(32160,11). +sustain(32240,11). +sustain(32320,11). +sustain(32640,11). +sustain(32720,11). +sustain(32880,11). +sustain(32960,11). +sustain(33040,11). +sustain(33120,11). +sustain(33280,11). +sustain(33360,11). +sustain(33440,11). +sustain(33520,11). +sustain(33680,11). +sustain(33760,11). +sustain(33840,11). +sustain(33920,11). +sustain(34240,11). +sustain(34320,11). +sustain(34720,11). +sustain(34800,11). +sustain(35040,11). +sustain(35120,11). +sustain(35680,11). +sustain(35840,11). +sustain(35920,11). +sustain(36400,12). +sustain(36480,13). +sustain(36720,14). +sustain(36960,15). +sustain(37040,15). +sustain(37200,16). +sustain(37280,16). +sustain(37360,17). +sustain(37440,18). +sustain(37520,19). +sustain(37600,20). +sustain(37680,21). +sustain(37760,21). +sustain(37840,22). +sustain(38000,22). +sustain(38240,23). +sustain(38400,24). +sustain(38480,25). +sustain(38560,27). +sustain(38640,30). +sustain(38720,36). +sustain(38800,41). +sustain(38880,47). +sustain(38960,52). +sustain(39040,58). +sustain(39120,64). +sustain(39200,70). +sustain(39280,75). +sustain(39360,80). +sustain(39440,84). +sustain(39520,86). +sustain(39600,88). +sustain(39680,89). +sustain(39760,89). +sustain(40000,90). +sustain(40080,92). +sustain(40160,94). +sustain(40240,97). +sustain(40320,101). +sustain(40400,104). +sustain(40480,107). +sustain(40560,109). +sustain(40640,112). +sustain(40720,113). +sustain(40800,115). +sustain(40880,116). +sustain(40960,117). +sustain(41040,117). +sustain(41360,118). +sustain(41440,118). +sustain(41520,119). +sustain(41600,120). +sustain(41680,120). +sustain(41760,121). +sustain(41920,122). +sustain(42080,121). +sustain(42240,122). +sustain(42640,121). +sustain(42800,122). +sustain(43200,121). +sustain(43280,122). +sustain(43360,121). +sustain(43440,122). +sustain(43920,121). +sustain(44160,122). +sustain(44640,121). +sustain(44800,122). +sustain(44960,121). +sustain(45120,120). +sustain(45200,119). +sustain(45280,118). +sustain(45360,118). +sustain(45520,116). +sustain(45600,115). +sustain(45680,112). +sustain(45760,109). +sustain(45840,105). +sustain(45920,102). +sustain(46000,98). +sustain(46080,94). +sustain(46160,90). +sustain(46240,86). +sustain(46320,84). +sustain(46400,82). +sustain(46480,81). +sustain(46560,80). +sustain(46640,78). +sustain(46720,75). +sustain(46800,71). +sustain(46880,67). +sustain(46960,62). +sustain(47040,57). +sustain(47120,51). +sustain(47200,45). +sustain(47280,39). +sustain(47360,33). +sustain(47440,28). +sustain(47520,24). +sustain(47600,20). +sustain(47680,19). +sustain(47760,18). +sustain(47840,18). +sustain(47920,17). +sustain(48080,18). +sustain(48160,17). +sustain(48720,18). +sustain(48800,19). +sustain(48880,20). +sustain(48960,22). +sustain(49040,25). +sustain(49120,28). +sustain(49200,30). +sustain(49280,32). +sustain(49360,35). +sustain(49440,41). +sustain(49520,49). +sustain(49600,56). +sustain(49680,64). +sustain(49760,71). +sustain(49840,78). +sustain(49920,85). +sustain(50000,92). +sustain(50080,98). +sustain(50160,102). +sustain(50240,104). +sustain(50320,105). +sustain(51040,106). +sustain(51120,107). +sustain(51200,108). +sustain(51280,110). +sustain(51360,112). +sustain(51440,113). +sustain(61600,113). +sustain(63600,112). +sustain(63680,110). +sustain(63760,109). +sustain(63840,107). +sustain(63920,106). +sustain(64000,103). +sustain(64080,101). +sustain(64160,97). +sustain(64240,92). +sustain(64320,88). +sustain(64400,84). +sustain(64480,81). +sustain(64560,81). +sustain(64640,79). +sustain(64720,77). +sustain(64800,74). +sustain(64880,71). +sustain(64960,68). +sustain(65040,65). +sustain(65120,61). +sustain(65200,55). +sustain(65280,48). +sustain(65360,40). +sustain(65440,32). +sustain(65520,25). +sustain(65600,19). +sustain(65680,15). +sustain(65760,14). +sustain(65840,12). +sustain(65920,11). +sustain(66080,10). +sustain(66240,10). +sustain(67520,10). +sustain(67760,11). +sustain(67920,11). +sustain(68080,12). +sustain(68160,12). +sustain(68240,13). +sustain(68320,14). +sustain(68400,14). +sustain(68480,15). +sustain(68560,15). +sustain(68640,16). +sustain(68880,16). +sustain(68960,17). +sustain(69120,17). +sustain(69200,18). +sustain(69280,18). +sustain(69360,19). +sustain(69440,20). +sustain(69520,21). +sustain(69600,21). +sustain(69680,22). +sustain(69760,22). +sustain(69840,23). +sustain(69920,23). +sustain(70000,24). +sustain(70080,24). +sustain(70240,25). +sustain(70320,25). +sustain(70400,26). +sustain(70480,27). +sustain(70560,30). +sustain(70640,35). +sustain(70720,41). +sustain(70800,48). +sustain(70880,54). +sustain(70960,60). +sustain(71040,67). +sustain(71120,75). +sustain(71200,82). +sustain(71280,89). +sustain(71360,95). +sustain(71440,99). +sustain(71520,102). +sustain(71600,103). +sustain(80400,102). +sustain(80480,100). +sustain(80560,96). +sustain(80640,89). +sustain(80720,80). +sustain(80800,71). +sustain(80880,63). +sustain(80960,53). +sustain(81040,41). +sustain(81120,28). +sustain(81200,19). +sustain(81280,16). +sustain(81360,14). +sustain(81440,12). +sustain(81520,12). +sustain(81840,12). +sustain(81920,14). +sustain(82000,15). +sustain(82080,16). +sustain(82160,18). +sustain(82240,21). +sustain(82320,24). +sustain(82400,27). +sustain(82480,30). +sustain(82560,35). +sustain(82640,43). +sustain(82720,52). +sustain(82800,63). +sustain(82880,75). +sustain(82960,85). +sustain(83040,96). +sustain(83120,105). +sustain(83200,112). +sustain(83280,116). +sustain(83360,116). +sustain(93520,116). +sustain(96880,116). +sustain(96960,114). +sustain(97040,111). +sustain(97120,108). +sustain(97200,104). +sustain(97280,99). +sustain(97360,94). +sustain(97440,88). +sustain(97520,82). +sustain(97600,80). +sustain(97680,78). +sustain(97760,74). +sustain(97840,67). +sustain(97920,60). +sustain(98000,54). +sustain(98080,48). +sustain(98160,42). +sustain(98240,35). +sustain(98320,28). +sustain(98400,22). +sustain(98480,18). +sustain(98560,16). +sustain(98640,14). +sustain(98720,12). +sustain(98800,11). +sustain(98880,9). +sustain(98960,8). +sustain(99040,6). +sustain(99120,5). +sustain(100560,8). +sustain(100640,13). +sustain(100720,14). +sustain(100800,17). +sustain(100880,21). +sustain(100960,24). +sustain(101040,26). +sustain(101120,27). +sustain(101200,28). +sustain(101280,30). +sustain(101360,31). +sustain(101600,32). +sustain(101680,32). +sustain(101760,33). +sustain(101840,35). +sustain(101920,38). +sustain(102000,43). +sustain(102080,47). +sustain(102160,52). +sustain(102240,56). +sustain(102320,60). +sustain(102400,63). +sustain(102480,67). +sustain(102560,71). +sustain(102640,75). +sustain(102720,78). +sustain(102800,81). +sustain(102880,83). +sustain(102960,86). +sustain(103040,87). +sustain(103120,88). +sustain(103200,88). +sustain(103360,89). +sustain(103440,89). +sustain(103520,91). +sustain(103600,92). +sustain(103680,94). +sustain(103760,96). +sustain(103840,99). +sustain(103920,100). +sustain(104000,102). +sustain(104080,103). +sustain(104160,105). +sustain(104240,106). +sustain(104320,106). +sustain(105680,107). +sustain(105760,106). +sustain(106800,107). +sustain(106880,106). +sustain(106960,107). +sustain(107120,106). +sustain(107440,107). +sustain(107520,106). +sustain(107600,107). +sustain(107760,106). +sustain(108080,107). +sustain(108160,106). +sustain(108240,107). +sustain(108320,106). +sustain(109040,107). +sustain(109120,106). +sustain(109360,107). +sustain(109440,106). +sustain(109520,107). +sustain(109600,106). +sustain(109760,107). +sustain(109840,106). +sustain(110080,107). +sustain(110160,106). +sustain(110320,107). +sustain(110400,106). +sustain(110480,107). +sustain(110720,106). +sustain(110880,107). +sustain(110960,106). +sustain(111280,107). +sustain(111360,106). +sustain(111440,107). +sustain(111520,106). +sustain(112640,107). +sustain(112720,106). +sustain(112880,107). +sustain(112960,106). +sustain(113040,105). +sustain(113120,99). +sustain(113200,88). +sustain(113280,75). +sustain(113360,64). +sustain(113440,54). +sustain(113520,42). +sustain(113600,30). +sustain(113680,21). +sustain(113760,18). +sustain(113840,17). +sustain(113920,16). +sustain(114160,17). +sustain(114240,18). +sustain(114320,20). +sustain(114400,23). +sustain(114480,27). +sustain(114560,31). +sustain(114640,36). +sustain(114720,42). +sustain(114800,51). +sustain(114880,60). +sustain(114960,69). +sustain(115040,78). +sustain(115120,86). +sustain(115200,95). +sustain(115280,102). +sustain(115360,107). +sustain(115440,109). +sustain(115520,110). +sustain(125680,110). +sustain(129360,109). +sustain(129440,108). +sustain(129520,106). +sustain(129600,103). +sustain(129680,99). +sustain(129760,94). +sustain(129840,89). +sustain(129920,84). +sustain(130000,81). +sustain(130080,80). +sustain(130160,78). +sustain(130240,73). +sustain(130320,66). +sustain(130400,61). +sustain(130480,56). +sustain(130560,50). +sustain(130640,41). +sustain(130720,31). +sustain(130800,23). +sustain(130880,17). +sustain(130960,13). +sustain(131040,11). +sustain(131120,8). +sustain(131200,6). +sustain(131280,5). +sustain(132560,7). +sustain(132640,11). +sustain(132720,12). +sustain(132800,14). +sustain(132880,17). +sustain(132960,20). +sustain(133040,22). +sustain(133120,24). +sustain(133200,26). +sustain(133280,27). +sustain(133360,29). +sustain(133440,31). +sustain(133520,36). +sustain(133600,41). +sustain(133680,46). +sustain(133760,53). +sustain(133840,60). +sustain(133920,67). +sustain(134000,76). +sustain(134080,85). +sustain(134160,94). +sustain(134240,102). +sustain(134320,109). +sustain(134400,113). +sustain(134480,115). +sustain(136720,116). +sustain(136800,115). +sustain(137920,116). +sustain(138000,115). +sustain(138800,116). +sustain(138880,115). +sustain(139520,116). +sustain(139600,115). +sustain(140240,116). +sustain(140320,115). +sustain(140880,116). +sustain(140960,115). +sustain(141280,116). +sustain(141360,115). +sustain(142640,116). +sustain(142720,115). +sustain(144640,116). +sustain(144720,115). +sustain(145120,114). +sustain(145200,112). +sustain(145280,110). +sustain(145360,109). +sustain(145440,107). +sustain(145520,105). +sustain(145600,102). +sustain(145680,100). +sustain(145760,97). +sustain(145840,94). +sustain(145920,92). +sustain(146000,90). +sustain(146080,87). +sustain(146160,85). +sustain(146240,83). +sustain(146320,82). +sustain(146400,81). +sustain(146480,77). +sustain(146560,72). +sustain(146640,67). +sustain(146720,63). +sustain(146800,57). +sustain(146880,51). +sustain(146960,43). +sustain(147040,35). +sustain(147120,28). +sustain(147200,22). +sustain(147280,17). +sustain(147360,15). +sustain(147440,12). +sustain(147520,10). +sustain(147600,9). +sustain(147680,7). +sustain(147760,7). +sustain(148800,7). +sustain(148880,9). +sustain(148960,11). +sustain(149120,12). +sustain(149200,13). +sustain(149440,13). +sustain(149600,14). +sustain(149680,15). +sustain(149760,16). +sustain(149840,17). +sustain(149920,19). +sustain(150000,22). +sustain(150080,25). +sustain(150160,27). +sustain(150240,28). +sustain(150320,31). +sustain(150400,36). +sustain(150480,42). +sustain(150560,48). +sustain(150640,53). +sustain(150720,58). +sustain(150800,63). +sustain(150880,69). +sustain(150960,75). +sustain(151040,81). +sustain(151120,88). +sustain(151200,92). +sustain(151280,96). +sustain(151360,98). +sustain(151440,99). +sustain(151520,100). +sustain(156400,98). +sustain(156480,93). +sustain(156560,84). +sustain(156640,76). +sustain(156720,70). +sustain(156800,66). +sustain(156880,60). +sustain(156960,55). +sustain(157040,53). +sustain(157280,54). +sustain(157360,55). +sustain(157440,57). +sustain(157520,58). +sustain(157600,60). +sustain(157680,64). +sustain(157760,70). +sustain(157840,78). +sustain(157920,85). +sustain(158000,89). +sustain(158080,92). +sustain(158160,95). +sustain(158240,97). +sustain(158320,99). +sustain(158400,100). +sustain(160640,99). +sustain(160720,98). +sustain(160800,96). +sustain(160880,94). +sustain(160960,91). +sustain(161040,88). +sustain(161120,85). +sustain(161200,82). +sustain(161280,82). +sustain(161360,81). +sustain(161440,76). +sustain(161520,69). +sustain(161600,60). +sustain(161680,52). +sustain(161760,43). +sustain(161840,35). +sustain(161920,27). +sustain(162000,19). +sustain(162080,16). +sustain(162160,15). +sustain(162240,14). +sustain(162320,13). +sustain(162960,14). +sustain(163040,15). +sustain(163120,16). +sustain(163200,18). +sustain(163280,20). +sustain(163360,23). +sustain(163440,26). +sustain(163520,29). +sustain(163600,30). +sustain(163680,32). +sustain(163760,37). +sustain(163840,44). +sustain(163920,51). +sustain(164000,58). +sustain(164080,66). +sustain(164160,73). +sustain(164240,81). +sustain(164320,88). +sustain(164400,94). +sustain(164480,97). +sustain(164560,99). +sustain(166400,98). +sustain(166480,98). +sustain(166560,97). +sustain(166640,95). +sustain(166720,93). +sustain(166800,91). +sustain(166880,86). +sustain(166960,82). +sustain(167040,78). +sustain(167120,74). +sustain(167200,65). +sustain(167280,54). +sustain(167360,44). +sustain(167440,38). +sustain(167520,33). +sustain(167600,30). +sustain(167680,27). +sustain(167760,26). +sustain(168160,26). +sustain(168240,27). +sustain(168320,29). +sustain(168400,31). +sustain(168480,34). +sustain(168560,35). +sustain(168720,36). +sustain(168800,37). +sustain(168880,39). +sustain(168960,41). +sustain(169040,44). +sustain(169120,47). +sustain(169200,51). +sustain(169280,57). +sustain(169360,62). +sustain(169440,68). +sustain(169520,74). +sustain(169600,80). +sustain(169680,86). +sustain(169760,89). +sustain(169840,92). +sustain(169920,93). +sustain(174080,92). +sustain(174160,88). +sustain(174240,81). +sustain(174320,76). +sustain(174400,72). +sustain(174480,67). +sustain(174560,62). +sustain(174640,57). +sustain(174720,54). +sustain(175120,54). +sustain(175200,55). +sustain(175280,57). +sustain(175360,58). +sustain(175440,58). +sustain(175520,59). +sustain(175600,62). +sustain(175680,65). +sustain(175760,69). +sustain(175840,74). +sustain(175920,79). +sustain(176000,83). +sustain(176080,86). +sustain(176160,87). +sustain(177760,85). +sustain(177840,80). +sustain(177920,74). +sustain(178000,67). +sustain(178080,59). +sustain(178160,50). +sustain(178240,42). +sustain(178320,36). +sustain(178400,33). +sustain(178480,31). +sustain(178560,30). +sustain(178880,31). +sustain(178960,34). +sustain(179040,37). +sustain(179120,40). +sustain(179200,40). +sustain(179280,42). +sustain(179360,45). +sustain(179440,50). +sustain(179520,55). +sustain(179600,61). +sustain(179680,66). +sustain(179760,70). +sustain(179840,75). +sustain(179920,78). +sustain(180000,82). +sustain(180080,85). +sustain(180160,87). +sustain(182320,85). +sustain(182400,81). +sustain(182480,78). +sustain(182560,75). +sustain(182640,67). +sustain(182720,57). +sustain(182800,45). +sustain(182880,35). +sustain(182960,28). +sustain(183040,22). +sustain(183120,19). +sustain(183200,18). +sustain(183280,17). +sustain(183840,18). +sustain(183920,19). +sustain(184000,21). +sustain(184080,23). +sustain(184160,27). +sustain(184240,30). +sustain(184320,32). +sustain(184400,35). +sustain(184480,39). +sustain(184560,45). +sustain(184640,52). +sustain(184720,59). +sustain(184800,66). +sustain(184880,72). +sustain(184960,78). +sustain(185040,83). +sustain(185120,86). +sustain(185200,88). +sustain(193920,87). +sustain(194000,86). +sustain(194080,85). +sustain(194160,83). +sustain(194240,81). +sustain(194320,81). +sustain(194400,80). +sustain(194480,78). +sustain(194560,75). +sustain(194640,71). +sustain(194720,66). +sustain(194800,62). +sustain(194880,58). +sustain(194960,53). +sustain(195040,47). +sustain(195120,42). +sustain(195200,37). +sustain(195280,31). +sustain(195360,27). +sustain(195440,23). +sustain(195520,21). +sustain(195600,20). +sustain(195680,18). +sustain(195760,17). +sustain(195840,17). +sustain(195920,16). +sustain(196000,15). +sustain(196080,15). +sustain(196160,14). +sustain(196240,13). +sustain(196320,12). +sustain(196400,12). +sustain(196480,11). +sustain(196640,11). +sustain(198560,12). +sustain(198640,14). +sustain(198720,15). +sustain(198800,16). +sustain(198880,19). +sustain(198960,22). +sustain(199040,24). +sustain(199120,25). +sustain(199200,26). +sustain(199280,27). +sustain(199360,27). +sustain(199440,28). +sustain(199520,29). +sustain(199600,30). +sustain(199680,31). +sustain(199760,34). +sustain(199840,38). +sustain(199920,43). +sustain(200000,49). +sustain(200080,56). +sustain(200160,64). +sustain(200240,72). +sustain(200320,80). +sustain(200400,87). +sustain(200480,94). +sustain(200560,100). +sustain(200640,104). +sustain(200720,107). +sustain(200800,108). +sustain(210800,108). +sustain(210880,107). +sustain(210960,105). +sustain(211040,103). +sustain(211120,102). +sustain(211200,101). +sustain(211280,99). +sustain(211360,97). +sustain(211440,95). +sustain(211520,93). +sustain(211600,91). +sustain(211680,89). +sustain(211760,88). +sustain(211840,87). +sustain(211920,86). +sustain(212000,85). +sustain(212080,84). +sustain(212160,84). +sustain(212240,83). +sustain(212320,83). +sustain(212400,82). +sustain(212480,82). +sustain(212560,81). +sustain(212640,80). +sustain(212720,78). +sustain(212800,76). +sustain(212880,72). +sustain(212960,68). +sustain(213040,65). +sustain(213120,61). +sustain(213200,56). +sustain(213280,52). +sustain(213360,47). +sustain(213440,42). +sustain(213520,38). +sustain(213600,33). +sustain(213680,29). +sustain(213760,25). +sustain(213840,21). +sustain(213920,19). +sustain(214000,16). +sustain(214080,15). +sustain(214160,13). +sustain(214240,12). +sustain(214320,12). +sustain(214560,11). +sustain(214720,12). +sustain(214800,11). +sustain(214960,12). +sustain(215120,13). +sustain(215200,15). +sustain(215280,16). +sustain(215360,19). +sustain(215440,22). +sustain(215520,25). +sustain(215600,27). +sustain(215680,28). +sustain(215760,29). +sustain(215840,30). +sustain(215920,32). +sustain(216000,35). +sustain(216080,37). +sustain(216160,40). +sustain(216240,44). +sustain(216320,49). +sustain(216400,55). +sustain(216480,61). +sustain(216560,68). +sustain(216640,76). +sustain(216720,82). +sustain(216800,89). +sustain(216880,94). +sustain(216960,98). +sustain(217040,101). +sustain(217120,102). +sustain(225280,101). +sustain(225360,100). +sustain(225440,98). +sustain(225520,95). +sustain(225600,92). +sustain(225680,88). +sustain(225760,85). +sustain(225840,82). +sustain(225920,81). +sustain(226000,79). +sustain(226080,74). +sustain(226160,66). +sustain(226240,56). +sustain(226320,47). +sustain(226400,39). +sustain(226480,29). +sustain(226560,20). +sustain(226640,14). +sustain(226720,12). +sustain(226800,9). +sustain(226880,6). +sustain(226960,5). +sustain(227040,5). +sustain(227120,5). +sustain(227200,5). +sustain(227440,5). +sustain(227760,6). +sustain(227840,9). +sustain(227920,11). +sustain(228000,12). +sustain(228080,14). +sustain(228160,17). +sustain(228240,19). +sustain(228320,21). +sustain(228400,23). +sustain(228480,24). +sustain(228560,24). +sustain(228640,25). +sustain(228720,25). +sustain(228800,26). +sustain(228880,27). +sustain(228960,29). +sustain(229040,31). +sustain(229120,35). +sustain(229200,40). +sustain(229280,47). +sustain(229360,53). +sustain(229440,61). +sustain(229520,68). +sustain(229600,75). +sustain(229680,82). +sustain(229760,87). +sustain(229840,92). +sustain(229920,95). +sustain(230000,97). +sustain(230080,98). +sustain(230480,98). +sustain(230560,98). +sustain(231040,98). +sustain(231120,98). +sustain(231280,98). +sustain(231360,98). +sustain(231520,98). +sustain(231600,98). +sustain(231760,98). +sustain(231840,98). +sustain(232080,98). +sustain(238640,98). +sustain(238720,96). +sustain(238800,94). +sustain(238880,91). +sustain(238960,87). +sustain(239040,83). +sustain(239120,81). +sustain(239200,80). +sustain(239280,79). +sustain(239360,75). +sustain(239440,69). +sustain(239520,65). +sustain(239600,60). +sustain(239680,56). +sustain(239760,52). +sustain(239840,46). +sustain(239920,40). +sustain(240000,34). +sustain(240080,29). +sustain(240160,23). +sustain(240240,18). +sustain(240320,16). +sustain(240400,13). +sustain(240480,11). +sustain(240560,8). +sustain(240640,6). +sustain(240720,5). +sustain(242320,6). +sustain(242400,7). +sustain(242480,8). +sustain(242560,8). +sustain(242640,9). +sustain(242720,10). +sustain(242880,10). +sustain(242960,12). +sustain(243040,12). +sustain(243120,13). +sustain(243200,14). +sustain(243280,15). +sustain(243360,15). +sustain(243440,16). +sustain(243520,17). +sustain(243600,18). +sustain(243680,19). +sustain(243760,20). +sustain(243840,21). +sustain(243920,23). +sustain(244000,24). +sustain(244080,25). +sustain(244160,26). +sustain(244240,28). +sustain(244320,30). +sustain(244400,33). +sustain(244480,36). +sustain(244560,40). +sustain(244640,45). +sustain(244720,50). +sustain(244800,56). +sustain(244880,63). +sustain(244960,71). +sustain(245040,78). +sustain(245120,85). +sustain(245200,90). +sustain(245280,94). +sustain(245360,97). +sustain(245440,99). +sustain(245760,100). +sustain(245840,99). +sustain(246000,100). +sustain(246080,99). +sustain(246160,100). +sustain(246800,100). +sustain(246960,100). +sustain(247040,100). +sustain(247440,101). +sustain(247760,101). +sustain(248240,102). +sustain(249440,102). +sustain(249520,102). +sustain(249600,102). +sustain(249680,102). +sustain(249760,102). +sustain(249920,102). +sustain(250080,102). +sustain(250240,102). +sustain(250400,102). +sustain(250560,102). +sustain(250640,102). +sustain(250880,102). +sustain(251040,102). +sustain(251120,102). +sustain(251200,102). +sustain(251760,102). +sustain(251840,102). +sustain(252000,102). +sustain(252080,102). +sustain(252160,102). +sustain(252240,102). +sustain(252400,102). +sustain(252480,102). +sustain(253280,102). +sustain(253360,101). +sustain(253440,99). +sustain(253520,97). +sustain(253600,94). +sustain(253680,90). +sustain(253760,86). +sustain(253840,81). +sustain(253920,77). +sustain(254000,71). +sustain(254080,60). +sustain(254160,44). +sustain(254240,28). +sustain(254320,17). +sustain(254400,12). +sustain(254480,8). +sustain(254560,3). +sustain(254640,1). +sustain(255280,2). +sustain(255360,3). +sustain(255440,8). +sustain(255520,12). +sustain(255600,13). +sustain(255680,17). +sustain(255760,21). +sustain(255840,24). +sustain(255920,27). +sustain(256000,29). +sustain(256080,34). +sustain(256160,40). +sustain(256240,45). +sustain(256320,51). +sustain(256400,55). +sustain(256480,60). +sustain(256560,66). +sustain(256640,73). +sustain(256720,79). +sustain(256800,84). +sustain(256880,88). +sustain(256960,91). +sustain(257040,93). +sustain(257120,95). +sustain(257200,95). +sustain(258000,96). +sustain(260720,96). +sustain(266640,95). +sustain(266720,94). +sustain(266800,92). +sustain(266880,88). +sustain(266960,83). +sustain(267040,79). +sustain(267120,75). +sustain(267200,69). +sustain(267280,57). +sustain(267360,41). +sustain(267440,26). +sustain(267520,15). +sustain(267600,11). +sustain(267680,7). +sustain(267760,2). +sustain(267840,1). +sustain(268640,5). +sustain(268720,11). +sustain(268800,14). +sustain(268880,17). +sustain(268960,24). +sustain(269040,30). +sustain(269120,37). +sustain(269200,47). +sustain(269280,59). +sustain(269360,71). +sustain(269440,81). +sustain(269520,91). +sustain(269600,98). +sustain(269680,106). +sustain(269760,112). +sustain(269840,115). +sustain(269920,116). +sustain(274000,116). +sustain(274080,116). +sustain(274240,116). +sustain(282080,116). +sustain(282160,113). +sustain(282240,109). +sustain(282320,103). +sustain(282400,94). +sustain(282480,82). +sustain(282560,70). +sustain(282640,60). +sustain(282720,47). +sustain(282800,32). +sustain(282880,20). +sustain(282960,15). +sustain(283040,15). +sustain(283120,12). +sustain(283200,11). +sustain(283680,13). +sustain(283760,14). +sustain(283840,16). +sustain(283920,19). +sustain(284000,23). +sustain(284080,27). +sustain(284160,31). +sustain(284240,35). +sustain(284320,41). +sustain(284400,50). +sustain(284480,60). +sustain(284560,69). +sustain(284640,78). +sustain(284720,86). +sustain(284800,93). +sustain(284880,99). +sustain(284960,104). +sustain(285040,107). +sustain(285120,108). +sustain(287360,108). +sustain(287440,107). +sustain(287520,104). +sustain(287600,99). +sustain(287680,91). +sustain(287760,79). +sustain(287840,69). +sustain(287920,60). +sustain(288000,48). +sustain(288080,35). +sustain(288160,25). +sustain(288240,20). +sustain(288400,19). +sustain(288720,20). +sustain(288800,23). +sustain(288880,26). +sustain(288960,30). +sustain(289040,33). +sustain(289120,37). +sustain(289200,43). +sustain(289280,52). +sustain(289360,62). +sustain(289440,71). +sustain(289520,80). +sustain(289600,88). +sustain(289680,96). +sustain(289760,102). +sustain(289840,109). +sustain(289920,112). +sustain(290000,112). +sustain(300160,112). +sustain(300960,112). +sustain(301120,111). +sustain(301200,111). +sustain(301360,110). +sustain(301440,110). +sustain(301520,109). +sustain(301600,109). +sustain(301680,108). +sustain(301760,107). +sustain(301840,106). +sustain(301920,104). +sustain(302000,103). +sustain(302080,101). +sustain(302160,98). +sustain(302240,95). +sustain(302320,93). +sustain(302400,90). +sustain(302480,88). +sustain(302560,87). +sustain(302640,86). +sustain(302720,85). +sustain(302800,85). +sustain(302880,84). +sustain(302960,84). +sustain(303040,83). +sustain(303120,82). +sustain(303200,81). +sustain(303280,79). +sustain(303360,76). +sustain(303440,72). +sustain(303520,69). +sustain(303600,66). +sustain(303680,62). +sustain(303760,58). +sustain(303840,55). +sustain(303920,51). +sustain(304000,48). +sustain(304080,46). +sustain(304160,43). +sustain(304240,41). +sustain(304320,38). +sustain(304400,36). +sustain(304480,34). +sustain(304560,32). +sustain(304640,30). +sustain(304720,29). +sustain(304800,27). +sustain(304880,25). +sustain(304960,24). +sustain(305040,23). +sustain(305120,22). +sustain(305200,21). +sustain(305280,20). +sustain(305360,19). +sustain(305440,18). +sustain(305520,18). +sustain(305600,17). +sustain(305680,17). +sustain(305760,16). +sustain(305840,15). +sustain(306000,15). +sustain(306880,15). +sustain(306960,16). +sustain(307040,17). +sustain(307120,18). +sustain(307200,18). +sustain(307280,19). +sustain(307360,19). +sustain(307440,20). +sustain(307600,20). +sustain(307680,21). +sustain(307760,22). +sustain(307840,23). +sustain(307920,24). +sustain(308000,26). +sustain(308080,28). +sustain(308160,29). +sustain(308240,29). +sustain(308320,30). +sustain(308400,31). +sustain(308480,32). +sustain(308560,32). +sustain(308640,33). +sustain(308800,33). +sustain(308880,35). +sustain(308960,37). +sustain(309040,41). +sustain(309120,45). +sustain(309200,50). +sustain(309280,56). +sustain(309360,62). +sustain(309440,67). +sustain(309520,73). +sustain(309600,77). +sustain(309680,81). +sustain(309760,85). +sustain(309840,87). +sustain(309920,88). +sustain(319520,87). +sustain(319600,86). +sustain(319680,85). +sustain(319760,85). +sustain(319920,84). +sustain(320000,82). +sustain(320080,82). +sustain(320240,81). +sustain(320320,81). +sustain(320400,80). +sustain(320480,79). +sustain(320560,77). +sustain(320640,75). +sustain(320720,71). +sustain(320800,68). +sustain(320880,65). +sustain(320960,62). +sustain(321040,59). +sustain(321120,55). +sustain(321200,52). +sustain(321280,48). +sustain(321360,45). +sustain(321440,42). +sustain(321520,39). +sustain(321600,35). +sustain(321680,32). +sustain(321760,29). +sustain(321840,26). +sustain(321920,24). +sustain(322000,22). +sustain(322080,21). +sustain(322160,20). +sustain(322240,19). +sustain(322320,18). +sustain(322400,18). +sustain(322480,17). +sustain(322560,16). +sustain(322640,16). +sustain(322720,15). +sustain(322800,15). +sustain(322880,14). +sustain(322960,14). +sustain(323040,13). +sustain(323120,13). +sustain(323440,12). +sustain(323680,13). +sustain(324000,12). +sustain(324080,13). +sustain(324160,12). +sustain(324240,13). +sustain(324320,12). +sustain(324480,13). +sustain(324720,12). +sustain(324800,13). +sustain(325040,13). +sustain(325120,14). +sustain(325200,14). +sustain(325280,15). +sustain(325360,16). +sustain(325440,17). +sustain(325520,17). +sustain(325600,18). +sustain(325680,20). +sustain(325760,21). +sustain(325840,22). +sustain(325920,23). +sustain(326000,24). +sustain(326080,25). +sustain(326160,26). +sustain(326240,27). +sustain(326320,27). +sustain(326400,28). +sustain(326480,29). +sustain(326560,29). +sustain(326640,31). +sustain(326720,32). +sustain(326800,34). +sustain(326880,36). +sustain(326960,39). +sustain(327040,43). +sustain(327120,48). +sustain(327200,52). +sustain(327280,57). +sustain(327360,62). +sustain(327440,66). +sustain(327520,72). +sustain(327600,76). +sustain(327680,80). +sustain(327760,83). +sustain(327840,85). +sustain(327920,86). +sustain(328000,87). +sustain(335120,86). +sustain(335200,83). +sustain(335280,79). +sustain(335360,75). +sustain(335440,68). +sustain(335520,58). +sustain(335600,46). +sustain(335680,35). +sustain(335760,27). +sustain(335840,21). +sustain(335920,18). +sustain(336000,18). +sustain(336320,19). +sustain(336400,22). +sustain(336480,24). +sustain(336560,28). +sustain(336640,30). +sustain(336720,32). +sustain(336800,35). +sustain(336880,40). +sustain(336960,46). +sustain(337040,52). +sustain(337120,59). +sustain(337200,66). +sustain(337280,72). +sustain(337360,78). +sustain(337440,83). +sustain(337520,88). +sustain(337600,92). +sustain(337680,94). +sustain(337760,94). +sustain(347920,94). +sustain(351280,94). +sustain(351360,93). +sustain(351440,92). +sustain(351520,90). +sustain(351600,87). +sustain(351680,84). +sustain(351760,82). +sustain(351840,81). +sustain(351920,81). +sustain(352000,79). +sustain(352080,77). +sustain(352160,74). +sustain(352240,69). +sustain(352320,66). +sustain(352400,63). +sustain(352480,59). +sustain(352560,54). +sustain(352640,49). +sustain(352720,45). +sustain(352800,41). +sustain(352880,37). +sustain(352960,33). +sustain(353040,29). +sustain(353120,27). +sustain(353200,25). +sustain(353280,23). +sustain(353360,21). +sustain(353440,20). +sustain(353520,19). +sustain(353600,17). +sustain(353680,15). +sustain(353760,13). +sustain(353840,11). +sustain(353920,8). +sustain(354000,5). +sustain(354080,4). +sustain(354160,4). +sustain(354240,4). +sustain(354400,4). +sustain(354480,4). +sustain(354640,4). +sustain(354720,4). +sustain(354880,4). +sustain(354960,4). +sustain(355120,4). +sustain(355200,4). +sustain(355360,4). +sustain(355440,4). +sustain(355600,4). +sustain(355680,4). +sustain(355840,4). +sustain(355920,4). +sustain(356080,4). +sustain(356160,4). +sustain(356320,4). +sustain(356400,4). +sustain(356560,4). +sustain(356720,7). +sustain(356800,12). +sustain(356880,13). +sustain(356960,15). +sustain(357040,18). +sustain(357120,19). +sustain(357200,20). +sustain(357280,21). +sustain(357360,21). +sustain(357440,22). +sustain(357520,22). +sustain(357600,23). +sustain(357680,24). +sustain(357760,25). +sustain(357840,26). +sustain(357920,28). +sustain(358000,30). +sustain(358080,33). +sustain(358160,38). +sustain(358240,44). +sustain(358320,51). +sustain(358400,57). +sustain(358480,64). +sustain(358560,70). +sustain(358640,76). +sustain(358720,81). +sustain(358800,86). +sustain(358880,89). +sustain(358960,91). +sustain(359040,91). +sustain(366720,91). +sustain(366800,90). +sustain(366880,89). +sustain(366960,87). +sustain(367040,85). +sustain(367120,81). +sustain(367200,78). +sustain(367280,74). +sustain(367360,66). +sustain(367440,53). +sustain(367520,40). +sustain(367600,29). +sustain(367680,21). +sustain(367760,16). +sustain(367840,14). +sustain(367920,12). +sustain(368320,14). +sustain(368400,16). +sustain(368480,18). +sustain(368560,21). +sustain(368640,24). +sustain(368720,27). +sustain(368800,29). +sustain(368880,33). +sustain(368960,39). +sustain(369040,46). +sustain(369120,54). +sustain(369200,61). +sustain(369280,68). +sustain(369360,74). +sustain(369440,78). +sustain(369520,82). +sustain(369600,85). +sustain(369680,87). +sustain(379840,87). +sustain(383200,86). +sustain(383280,85). +sustain(383360,83). +sustain(383440,81). +sustain(383520,79). +sustain(383680,78). +sustain(383760,76). +sustain(383840,73). +sustain(383920,68). +sustain(384000,63). +sustain(384080,59). +sustain(384160,54). +sustain(384240,49). +sustain(384320,44). +sustain(384400,39). +sustain(384480,34). +sustain(384560,29). +sustain(384640,25). +sustain(384720,22). +sustain(384800,19). +sustain(384880,17). +sustain(384960,15). +sustain(385040,13). +sustain(385120,11). +sustain(385200,9). +sustain(385280,6). +sustain(385360,4). +sustain(386640,5). +sustain(386720,11). +sustain(386800,14). +sustain(386880,16). +sustain(386960,20). +sustain(387040,23). +sustain(387120,26). +sustain(387200,28). +sustain(387280,30). +sustain(387360,33). +sustain(387440,37). +sustain(387520,41). +sustain(387600,44). +sustain(387680,48). +sustain(387760,53). +sustain(387840,59). +sustain(387920,65). +sustain(388000,71). +sustain(388080,78). +sustain(388160,85). +sustain(388240,91). +sustain(388320,96). +sustain(388400,100). +sustain(388480,103). +sustain(388560,104). +sustain(389680,104). +sustain(398800,103). +sustain(398880,101). +sustain(398960,98). +sustain(399040,94). +sustain(399120,91). +sustain(399200,88). +sustain(399280,86). +sustain(399360,85). +sustain(399440,83). +sustain(399520,82). +sustain(399600,82). +sustain(399680,81). +sustain(399760,81). +sustain(399840,79). +sustain(399920,77). +sustain(400000,74). +sustain(400080,70). +sustain(400160,65). +sustain(400240,60). +sustain(400320,54). +sustain(400400,48). +sustain(400480,40). +sustain(400560,33). +sustain(400640,27). +sustain(400720,21). +sustain(400800,18). +sustain(400880,16). +sustain(400960,14). +sustain(401040,12). +sustain(401120,12). +sustain(401280,11). +sustain(402000,12). +sustain(402080,13). +sustain(402160,15). +sustain(402240,16). +sustain(402320,19). +sustain(402400,20). +sustain(402480,22). +sustain(402560,23). +sustain(402640,24). +sustain(402720,24). +sustain(402800,25). +sustain(402880,25). +sustain(402960,26). +sustain(403040,27). +sustain(403120,29). +sustain(403200,31). +sustain(403280,34). +sustain(403360,39). +sustain(403440,45). +sustain(403520,52). +sustain(403600,60). +sustain(403680,67). +sustain(403760,74). +sustain(403840,80). +sustain(403920,86). +sustain(404000,91). +sustain(404080,94). +sustain(404160,96). +sustain(404240,96). +sustain(414400,96). +sustain(414480,95). +sustain(414560,94). +sustain(414640,92). +sustain(414720,91). +sustain(414800,88). +sustain(414880,85). +sustain(414960,81). +sustain(415040,79). +sustain(415120,77). +sustain(415200,72). +sustain(415280,63). +sustain(415360,52). +sustain(415440,43). +sustain(415520,36). +sustain(415600,29). +sustain(415680,22). +sustain(415760,18). +sustain(415840,17). +sustain(415920,16). +sustain(416000,15). +sustain(416800,16). +sustain(416880,18). +sustain(416960,19). +sustain(417040,22). +sustain(417120,25). +sustain(417200,28). +sustain(417280,30). +sustain(417360,33). +sustain(417440,37). +sustain(417520,42). +sustain(417600,49). +sustain(417680,55). +sustain(417760,61). +sustain(417840,67). +sustain(417920,73). +sustain(418000,78). +sustain(418080,82). +sustain(418160,86). +sustain(418240,89). +sustain(418320,90). +sustain(418400,90). +sustain(418720,90). +sustain(419360,90). +sustain(419440,90). +sustain(419520,90). +sustain(419600,88). +sustain(419680,83). +sustain(419760,77). +sustain(419840,72). +sustain(419920,66). +sustain(420000,58). +sustain(420080,49). +sustain(420160,41). +sustain(420240,37). +sustain(420320,35). +sustain(420400,34). +sustain(420480,33). +sustain(420880,34). +sustain(420960,36). +sustain(421040,37). +sustain(421120,39). +sustain(421200,40). +sustain(421280,41). +sustain(421360,43). +sustain(421440,46). +sustain(421520,49). +sustain(421600,52). +sustain(421680,56). +sustain(421760,61). +sustain(421840,66). +sustain(421920,71). +sustain(422000,76). +sustain(422080,79). +sustain(422160,83). +sustain(422240,85). +sustain(422320,86). +sustain(423920,86). +sustain(424000,86). +sustain(424240,86). +sustain(424320,86). +sustain(426160,86). +sustain(426240,86). +sustain(426960,86). +sustain(427040,86). +sustain(427200,86). +sustain(427280,86). +sustain(427440,86). +sustain(427520,86). +sustain(427680,86). +sustain(427760,86). +sustain(427920,86). +sustain(428000,86). +sustain(429200,86). +sustain(429280,86). +sustain(430240,82). +sustain(430320,76). +sustain(430400,67). +sustain(430480,58). +sustain(430560,48). +sustain(430640,39). +sustain(430720,31). +sustain(430800,27). +sustain(430880,25). +sustain(431200,26). +sustain(431280,28). +sustain(431360,30). +sustain(431440,33). +sustain(431520,35). +sustain(431600,36). +sustain(431680,39). +sustain(431760,43). +sustain(431840,48). +sustain(431920,54). +sustain(432000,60). +sustain(432080,66). +sustain(432160,72). +sustain(432240,78). +sustain(432320,84). +sustain(432400,89). +sustain(432480,92). +sustain(432560,94). +sustain(432640,95). +sustain(434480,94). +sustain(434560,93). +sustain(434640,89). +sustain(434720,81). +sustain(434800,73). +sustain(434880,64). +sustain(434960,53). +sustain(435040,39). +sustain(435120,27). +sustain(435200,20). +sustain(435280,18). +sustain(435360,17). +sustain(435440,16). +sustain(435760,17). +sustain(435840,18). +sustain(435920,20). +sustain(436000,22). +sustain(436080,24). +sustain(436160,27). +sustain(436240,29). +sustain(436320,31). +sustain(436400,33). +sustain(436480,38). +sustain(436560,44). +sustain(436640,52). +sustain(436720,59). +sustain(436800,66). +sustain(436880,73). +sustain(436960,79). +sustain(437040,83). +sustain(437120,87). +sustain(437200,90). +sustain(437280,90). +sustain(446240,89). +sustain(446320,88). +sustain(446400,87). +sustain(446480,86). +sustain(446560,84). +sustain(446640,82). +sustain(446720,81). +sustain(446800,80). +sustain(446880,80). +sustain(446960,79). +sustain(447040,77). +sustain(447120,74). +sustain(447200,71). +sustain(447280,67). +sustain(447360,63). +sustain(447440,59). +sustain(447520,54). +sustain(447600,49). +sustain(447680,44). +sustain(447760,39). +sustain(447840,34). +sustain(447920,30). +sustain(448000,27). +sustain(448080,25). +sustain(448160,24). +sustain(448240,22). +sustain(448320,21). +sustain(448400,20). +sustain(448480,19). +sustain(448560,18). +sustain(448640,17). +sustain(448720,16). +sustain(448800,16). +sustain(448880,15). +sustain(448960,14). +sustain(449040,14). +sustain(449120,13). +sustain(449360,13). +sustain(449760,12). +sustain(451040,13). +sustain(451120,16). +sustain(451200,17). +sustain(451280,19). +sustain(451360,23). +sustain(451440,25). +sustain(451520,27). +sustain(451600,29). +sustain(451680,31). +sustain(451760,34). +sustain(451840,39). +sustain(451920,44). +sustain(452000,51). +sustain(452080,58). +sustain(452160,65). +sustain(452240,74). +sustain(452320,81). +sustain(452400,89). +sustain(452480,97). +sustain(452560,102). +sustain(452640,106). +sustain(452720,109). +sustain(454160,109). +sustain(462720,107). +sustain(462800,104). +sustain(462880,99). +sustain(462960,92). +sustain(463040,81). +sustain(463120,74). +sustain(463200,66). +sustain(463280,58). +sustain(463360,46). +sustain(463440,35). +sustain(463520,28). +sustain(463600,24). +sustain(463680,22). +sustain(463760,21). +sustain(463840,21). +sustain(464160,21). +sustain(464240,22). +sustain(464320,24). +sustain(464400,26). +sustain(464480,29). +sustain(464560,31). +sustain(464640,33). +sustain(464720,36). +sustain(464800,40). +sustain(464880,46). +sustain(464960,53). +sustain(465040,62). +sustain(465120,70). +sustain(465200,77). +sustain(465280,85). +sustain(465360,91). +sustain(465440,98). +sustain(465520,103). +sustain(465600,107). +sustain(465680,109). +sustain(475840,109). +sustain(477600,108). +sustain(477680,107). +sustain(477760,104). +sustain(477840,101). +sustain(477920,96). +sustain(478000,90). +sustain(478080,84). +sustain(478160,80). +sustain(478240,77). +sustain(478320,72). +sustain(478400,60). +sustain(478480,46). +sustain(478560,31). +sustain(478640,21). +sustain(478720,14). +sustain(478800,10). +sustain(478880,6). +sustain(478960,3). +sustain(479600,4). +sustain(479680,8). +sustain(479760,12). +sustain(479840,12). +sustain(479920,15). +sustain(480000,19). +sustain(480080,22). +sustain(480160,23). +sustain(480240,25). +sustain(480320,27). +sustain(480400,29). +sustain(480480,31). +sustain(480560,35). +sustain(480640,39). +sustain(480720,43). +sustain(480800,49). +sustain(480880,55). +sustain(480960,62). +sustain(481040,70). +sustain(481120,78). +sustain(481200,85). +sustain(481280,92). +sustain(481360,97). +sustain(481440,101). +sustain(481520,104). +sustain(481600,106). +sustain(481680,106). +sustain(485360,107). +sustain(491200,105). +sustain(491280,98). +sustain(491360,85). +sustain(491440,68). +sustain(491520,53). +sustain(491600,37). +sustain(491680,20). +sustain(491760,9). +sustain(491840,5). +sustain(491920,2). +sustain(492400,3). +sustain(492480,8). +sustain(492560,12). +sustain(492640,14). +sustain(492720,18). +sustain(492800,23). +sustain(492880,28). +sustain(492960,33). +sustain(493040,41). +sustain(493120,52). +sustain(493200,65). +sustain(493280,76). +sustain(493360,87). +sustain(493440,97). +sustain(493520,105). +sustain(493600,110). +sustain(493680,114). +sustain(493760,115). +sustain(495440,115). +sustain(495680,116). +sustain(495840,116). +sustain(496000,117). +sustain(496160,117). +sustain(496320,118). +sustain(496560,118). +sustain(496800,119). +sustain(497040,119). +sustain(497200,120). +sustain(497520,120). +sustain(498560,121). +sustain(498960,121). +sustain(499120,122). +sustain(499280,122). +sustain(500000,123). +sustain(500080,122). +sustain(500160,123). +sustain(500560,122). +sustain(500640,123). +sustain(500800,122). +sustain(500960,123). +sustain(501120,122). +sustain(501200,123). +sustain(501360,122). +sustain(501440,123). +sustain(501600,122). +sustain(501680,123). +sustain(501840,122). +sustain(501920,123). +sustain(502080,122). +sustain(502160,123). +sustain(502320,122). +sustain(502400,123). +sustain(502560,122). +sustain(502640,123). +sustain(502960,122). +sustain(503040,123). +sustain(503200,122). +sustain(503280,123). +sustain(503440,122). +sustain(503520,123). +sustain(503600,122). +sustain(503680,123). +sustain(503840,122). +sustain(503920,123). +sustain(504080,122). +sustain(504160,123). +sustain(504560,122). +sustain(504640,123). +sustain(504800,122). +sustain(504880,123). +sustain(505120,122). +sustain(505200,121). +sustain(505280,117). +sustain(505360,111). +sustain(505440,103). +sustain(505520,93). +sustain(505600,82). +sustain(505680,73). +sustain(505760,64). +sustain(505840,50). +sustain(505920,33). +sustain(506000,19). +sustain(506080,11). +sustain(506160,9). +sustain(506240,6). +sustain(506320,2). +sustain(507040,3). +sustain(507120,7). +sustain(507200,12). +sustain(507280,13). +sustain(507360,16). +sustain(507440,21). +sustain(507520,25). +sustain(507600,27). +sustain(507680,30). +sustain(507760,35). +sustain(507840,41). +sustain(507920,47). +sustain(508000,52). +sustain(508080,57). +sustain(508160,62). +sustain(508240,67). +sustain(508320,75). +sustain(508400,82). +sustain(508480,89). +sustain(508560,96). +sustain(508640,101). +sustain(508720,105). +sustain(508800,108). +sustain(508880,109). +sustain(508960,110). +sustain(509520,110). +sustain(509680,111). +sustain(509760,111). +sustain(510080,112). +sustain(512240,112). +sustain(512480,113). +sustain(512880,113). +sustain(513760,114). +sustain(513840,113). +sustain(514080,114). +sustain(514240,113). +sustain(514400,114). +sustain(514480,113). +sustain(514640,114). +sustain(514720,113). +sustain(514880,114). +sustain(514960,113). +sustain(515120,114). +sustain(515200,113). +sustain(515360,114). +sustain(515440,113). +sustain(515600,114). +sustain(515680,113). +sustain(515760,114). +sustain(516080,114). +sustain(519520,113). +sustain(519600,108). +sustain(519680,96). +sustain(519760,78). +sustain(519840,61). +sustain(519920,47). +sustain(520000,34). +sustain(520080,22). +sustain(520160,15). +sustain(520240,13). +sustain(520400,13). +sustain(520480,13). +sustain(520640,15). +sustain(520720,17). +sustain(520800,19). +sustain(520880,23). +sustain(520960,27). +sustain(521040,31). +sustain(521120,35). +sustain(521200,42). +sustain(521280,52). +sustain(521360,64). +sustain(521440,77). +sustain(521520,89). +sustain(521600,100). +sustain(521680,110). +sustain(521760,118). +sustain(521840,122). +sustain(521920,122). +sustain(528160,123). +sustain(528320,123). +sustain(528560,124). +sustain(528640,124). +sustain(528800,125). +sustain(528960,125). +sustain(529200,126). +sustain(529360,126). +sustain(529680,127). +sustain(530720,127). +sustain(534000,126). +sustain(534080,120). +sustain(534160,108). +sustain(534240,91). +sustain(534320,71). +sustain(534400,55). +sustain(534480,40). +sustain(534560,25). +sustain(534640,14). +sustain(534720,10). +sustain(534800,9). +sustain(534880,7). +sustain(534960,7). +sustain(535200,8). +sustain(535280,11). +sustain(535360,14). +sustain(535440,16). +sustain(535520,20). +sustain(535600,25). +sustain(535680,30). +sustain(535760,37). +sustain(535840,48). +sustain(535920,62). +sustain(536000,77). +sustain(536080,91). +sustain(536160,104). +sustain(536240,116). +sustain(536320,123). +sustain(536400,127). +sustain(537520,127). +sustain(541120,125). +sustain(541200,116). +sustain(541280,98). +sustain(541360,76). +sustain(541440,58). +sustain(541520,46). +sustain(541600,36). +sustain(541680,28). +sustain(541760,22). +sustain(542000,23). +sustain(542080,26). +sustain(542160,31). +sustain(542240,38). +sustain(542320,45). +sustain(542400,56). +sustain(542480,70). +sustain(542560,86). +sustain(542640,102). +sustain(542720,115). +sustain(542800,125). +sustain(542880,127). +sustain(545520,122). +sustain(545600,106). +sustain(545680,79). +sustain(545760,48). +sustain(545840,23). +sustain(545920,10). +sustain(546000,6). +sustain(546080,3). +sustain(546160,1). +sustain(546320,2). +sustain(546400,8). +sustain(546480,14). +sustain(546560,17). +sustain(546640,23). +sustain(546720,31). +sustain(546800,43). +sustain(546880,57). +sustain(546960,73). +sustain(547040,89). +sustain(547120,104). +sustain(547200,117). +sustain(547280,126). +sustain(547360,127). +sustain(552400,124). +sustain(552480,120). +sustain(552560,116). +sustain(552640,112). +sustain(552720,107). +sustain(552800,100). +sustain(552880,92). +sustain(552960,85). +sustain(553040,80). +sustain(553120,76). +sustain(553200,69). +sustain(553280,60). +sustain(553360,52). +sustain(553440,45). +sustain(553520,41). +sustain(553600,36). +sustain(553680,30). +sustain(553760,24). +sustain(553840,20). +sustain(553920,17). +sustain(554000,15). +sustain(554080,12). +sustain(554160,10). +sustain(554240,8). +sustain(554320,6). +sustain(554400,6). +sustain(558080,6). +sustain(558160,9). +sustain(558240,12). +sustain(558400,14). +sustain(558480,16). +sustain(558560,18). +sustain(558640,20). +sustain(558720,22). +sustain(558800,25). +sustain(558880,27). +sustain(558960,28). +sustain(559040,31). +sustain(559120,35). +sustain(559200,39). +sustain(559280,44). +sustain(559360,48). +sustain(559440,53). +sustain(559520,60). +sustain(559600,67). +sustain(559680,75). +sustain(559760,83). +sustain(559840,91). +sustain(559920,97). +sustain(560000,102). +sustain(560080,105). +sustain(560160,107). +sustain(560240,108). +sustain(560320,107). +sustain(560560,108). +sustain(560720,108). +sustain(560880,109). +sustain(560960,110). +sustain(561040,111). +sustain(561120,113). +sustain(561200,114). +sustain(561280,115). +sustain(563440,115). +sustain(573600,115). +sustain(577440,114). +sustain(577520,109). +sustain(577600,97). +sustain(577680,80). +sustain(577760,66). +sustain(577840,56). +sustain(577920,47). +sustain(578000,36). +sustain(578080,28). +sustain(578160,24). +sustain(578560,25). +sustain(578640,28). +sustain(578720,31). +sustain(578800,35). +sustain(578880,39). +sustain(578960,43). +sustain(579040,50). +sustain(579120,61). +sustain(579200,73). +sustain(579280,85). +sustain(579360,97). +sustain(579440,109). +sustain(579520,120). +sustain(579600,125). +sustain(579680,127). +sustain(581280,125). +sustain(581360,120). +sustain(581440,109). +sustain(581520,92). +sustain(581600,74). +sustain(581680,59). +sustain(581760,48). +sustain(581840,36). +sustain(581920,27). +sustain(582000,22). +sustain(582240,22). +sustain(582320,26). +sustain(582400,31). +sustain(582480,38). +sustain(582560,45). +sustain(582640,56). +sustain(582720,69). +sustain(582800,83). +sustain(582880,97). +sustain(582960,109). +sustain(583040,118). +sustain(583120,122). +sustain(585760,122). +sustain(585840,121). +sustain(585920,121). +sustain(586080,120). +sustain(586160,120). +sustain(586240,119). +sustain(586320,118). +sustain(586400,117). +sustain(586480,116). +sustain(586560,115). +sustain(586640,113). +sustain(586720,112). +sustain(586800,109). +sustain(586880,107). +sustain(586960,104). +sustain(587040,101). +sustain(587120,98). +sustain(587200,94). +sustain(587280,90). +sustain(587360,87). +sustain(587440,87). +sustain(587520,86). +sustain(587600,84). +sustain(587680,81). +sustain(587760,78). +sustain(587840,76). +sustain(587920,73). +sustain(588000,69). +sustain(588080,65). +sustain(588160,61). +sustain(588240,57). +sustain(588320,53). +sustain(588400,50). +sustain(588480,47). +sustain(588560,44). +sustain(588640,42). +sustain(588720,41). +sustain(588800,39). +sustain(588880,39). +sustain(588960,38). +sustain(589040,37). +sustain(589120,36). +sustain(589200,36). +sustain(589280,35). +sustain(589360,34). +sustain(589440,33). +sustain(589520,32). +sustain(589600,30). +sustain(589680,29). +sustain(589760,28). +sustain(589840,27). +sustain(589920,26). +sustain(590000,25). +sustain(590080,25). +sustain(590160,24). +sustain(590240,24). +sustain(590320,23). +sustain(590400,22). +sustain(590480,22). +sustain(590560,21). +sustain(590640,21). +sustain(590800,20). +sustain(590880,20). +sustain(590960,19). +sustain(591040,19). +sustain(591200,18). +sustain(592160,19). +sustain(592240,21). +sustain(592320,22). +sustain(592400,24). +sustain(592480,27). +sustain(592560,30). +sustain(592640,33). +sustain(592720,33). +sustain(592800,34). +sustain(592880,35). +sustain(592960,37). +sustain(593040,39). +sustain(593120,42). +sustain(593200,44). +sustain(593280,47). +sustain(593360,51). +sustain(593440,55). +sustain(593520,59). +sustain(593600,63). +sustain(593680,67). +sustain(593760,70). +sustain(593840,74). +sustain(593920,77). +sustain(594000,80). +sustain(594080,83). +sustain(594160,86). +sustain(594240,88). +sustain(594320,90). +sustain(594400,90). +sustain(594880,91). +sustain(595120,91). +sustain(595200,92). +sustain(595280,92). +sustain(595360,93). +sustain(595440,94). +sustain(595520,94). +sustain(595600,95). +sustain(595680,95). +sustain(596000,96). +sustain(596080,96). +sustain(596160,97). +sustain(596240,98). +sustain(596320,99). +sustain(596400,100). +sustain(596480,101). +sustain(596560,102). +sustain(596640,103). +sustain(596720,103). +sustain(597280,104). +sustain(597360,105). +sustain(597440,106). +sustain(597520,107). +sustain(597600,109). +sustain(597680,110). +sustain(597760,110). +sustain(598960,111). +sustain(599440,111). +sustain(599920,112). +sustain(600080,112). +sustain(600160,113). +sustain(600240,113). +sustain(600480,114). +sustain(603840,112). +sustain(603920,106). +sustain(604000,98). +sustain(604080,86). +sustain(604160,77). +sustain(604240,71). +sustain(604320,65). +sustain(604400,56). +sustain(604480,47). +sustain(604560,41). +sustain(604640,40). +sustain(604800,39). +sustain(605200,40). +sustain(605280,42). +sustain(605360,44). +sustain(605440,46). +sustain(605520,47). +sustain(605600,48). +sustain(605680,52). +sustain(605760,58). +sustain(605840,67). +sustain(605920,76). +sustain(606000,86). +sustain(606080,96). +sustain(606160,103). +sustain(606240,110). +sustain(606320,116). +sustain(606400,116). +sustain(606720,117). +sustain(606800,116). +sustain(606960,117). +sustain(607120,116). +sustain(607200,116). +sustain(607280,111). +sustain(607360,101). +sustain(607440,85). +sustain(607520,71). +sustain(607600,63). +sustain(607680,56). +sustain(607760,47). +sustain(607840,39). +sustain(607920,36). +sustain(608000,36). +sustain(608160,36). +sustain(608240,38). +sustain(608320,42). +sustain(608400,46). +sustain(608480,50). +sustain(608560,55). +sustain(608640,65). +sustain(608720,78). +sustain(608800,91). +sustain(608880,103). +sustain(608960,114). +sustain(609040,121). +sustain(609120,124). +sustain(610400,123). +sustain(610480,120). +sustain(610560,112). +sustain(610640,96). +sustain(610720,76). +sustain(610800,58). +sustain(610880,44). +sustain(610960,31). +sustain(611040,21). +sustain(611120,16). +sustain(611600,18). +sustain(611680,20). +sustain(611760,22). +sustain(611840,27). +sustain(611920,31). +sustain(612000,37). +sustain(612080,43). +sustain(612160,51). +sustain(612240,62). +sustain(612320,75). +sustain(612400,87). +sustain(612480,100). +sustain(612560,111). +sustain(612640,119). +sustain(612720,122). +sustain(612880,122). +sustain(612960,122). +sustain(613200,122). +sustain(613280,122). +sustain(613440,122). +sustain(613760,122). +sustain(613920,122). +sustain(614080,122). +sustain(614160,119). +sustain(614240,111). +sustain(614320,97). +sustain(614400,80). +sustain(614480,66). +sustain(614560,57). +sustain(614640,48). +sustain(614720,39). +sustain(614800,33). +sustain(614880,33). +sustain(614960,33). +sustain(615040,35). +sustain(615120,39). +sustain(615200,44). +sustain(615280,50). +sustain(615360,57). +sustain(615440,67). +sustain(615520,79). +sustain(615600,92). +sustain(615680,102). +sustain(615760,110). +sustain(615840,115). +sustain(615920,116). +sustain(618880,116). +sustain(618960,115). +sustain(619040,114). +sustain(619120,112). +sustain(619200,110). +sustain(619280,108). +sustain(619360,106). +sustain(619440,102). +sustain(619520,99). +sustain(619600,95). +sustain(619680,91). +sustain(619760,88). +sustain(619840,86). +sustain(619920,85). +sustain(620000,82). +sustain(620080,77). +sustain(620160,72). +sustain(620240,68). +sustain(620320,65). +sustain(620400,61). +sustain(620480,56). +sustain(620560,49). +sustain(620640,43). +sustain(620720,37). +sustain(620800,30). +sustain(620880,24). +sustain(620960,19). +sustain(621040,18). +sustain(621120,16). +sustain(621200,15). +sustain(621280,15). +sustain(622320,15). +sustain(622400,17). +sustain(622480,18). +sustain(622560,20). +sustain(622640,22). +sustain(622720,24). +sustain(622800,27). +sustain(622880,30). +sustain(622960,31). +sustain(623040,32). +sustain(623120,33). +sustain(623200,37). +sustain(623280,41). +sustain(623360,47). +sustain(623440,52). +sustain(623520,57). +sustain(623600,63). +sustain(623680,69). +sustain(623760,76). +sustain(623840,82). +sustain(623920,88). +sustain(624000,93). +sustain(624080,96). +sustain(624160,98). +sustain(624240,98). +sustain(627360,97). +sustain(627440,94). +sustain(627520,86). +sustain(627600,77). +sustain(627680,69). +sustain(627760,62). +sustain(627840,53). +sustain(627920,44). +sustain(628000,36). +sustain(628080,32). +sustain(628160,30). +sustain(628240,30). +sustain(628400,29). +sustain(628560,30). +sustain(628640,30). +sustain(628720,32). +sustain(628800,35). +sustain(628880,38). +sustain(628960,40). +sustain(629120,41). +sustain(629200,43). +sustain(629280,47). +sustain(629360,52). +sustain(629440,57). +sustain(629520,62). +sustain(629600,67). +sustain(629680,72). +sustain(629760,77). +sustain(629840,83). +sustain(629920,89). +sustain(630000,92). +sustain(630080,94). +sustain(630160,94). +sustain(631040,95). +sustain(631440,95). +sustain(635760,93). +sustain(635840,88). +sustain(635920,81). +sustain(636000,75). +sustain(636080,68). +sustain(636160,59). +sustain(636240,49). +sustain(636320,40). +sustain(636400,34). +sustain(636480,31). +sustain(636560,29). +sustain(636640,27). +sustain(637120,28). +sustain(637200,29). +sustain(637280,31). +sustain(637360,33). +sustain(637440,37). +sustain(637520,39). +sustain(637600,40). +sustain(637680,42). +sustain(637760,48). +sustain(637840,57). +sustain(637920,66). +sustain(638000,75). +sustain(638080,82). +sustain(638160,89). +sustain(638240,95). +sustain(638320,100). +sustain(638400,102). +sustain(638480,103). +sustain(640080,101). +sustain(640160,94). +sustain(640240,82). +sustain(640320,72). +sustain(640400,66). +sustain(640480,60). +sustain(640560,52). +sustain(640640,45). +sustain(640720,43). +sustain(640960,43). +sustain(641040,46). +sustain(641120,49). +sustain(641200,51). +sustain(641280,53). +sustain(641360,56). +sustain(641440,62). +sustain(641520,71). +sustain(641600,80). +sustain(641680,88). +sustain(641760,93). +sustain(641840,97). +sustain(641920,100). +sustain(642000,101). +sustain(642080,102). +sustain(643600,101). +sustain(643680,94). +sustain(643760,83). +sustain(643840,73). +sustain(643920,66). +sustain(644000,59). +sustain(644080,51). +sustain(644160,44). +sustain(644240,39). +sustain(644320,39). +sustain(644720,40). +sustain(644800,43). +sustain(644880,47). +sustain(644960,49). +sustain(645040,50). +sustain(645120,53). +sustain(645200,59). +sustain(645280,66). +sustain(645360,74). +sustain(645440,79). +sustain(645520,85). +sustain(645600,89). +sustain(645680,94). +sustain(645760,97). +sustain(645840,99). +sustain(645920,100). +sustain(647360,99). +sustain(647440,93). +sustain(647520,82). +sustain(647600,71). +sustain(647680,63). +sustain(647760,56). +sustain(647840,48). +sustain(647920,42). +sustain(648000,40). +sustain(648160,40). +sustain(648240,42). +sustain(648320,46). +sustain(648400,50). +sustain(648480,53). +sustain(648560,57). +sustain(648640,65). +sustain(648720,74). +sustain(648800,82). +sustain(648880,90). +sustain(648960,95). +sustain(649040,98). +sustain(649120,101). +sustain(649200,102). +sustain(652240,101). +sustain(652320,99). +sustain(652400,95). +sustain(652480,92). +sustain(652560,88). +sustain(652640,85). +sustain(652720,83). +sustain(652800,82). +sustain(652880,80). +sustain(652960,74). +sustain(653040,68). +sustain(653120,63). +sustain(653200,59). +sustain(653280,54). +sustain(653360,48). +sustain(653440,42). +sustain(653520,37). +sustain(653600,32). +sustain(653680,28). +sustain(653760,25). +sustain(653840,23). +sustain(653920,21). +sustain(654000,20). +sustain(654080,19). +sustain(654160,18). +sustain(654240,18). +sustain(654320,17). +sustain(654400,17). +sustain(654480,16). +sustain(654560,16). +sustain(655600,17). +sustain(655680,18). +sustain(655760,19). +sustain(655840,20). +sustain(655920,21). +sustain(656000,22). +sustain(656080,23). +sustain(656160,25). +sustain(656240,26). +sustain(656320,27). +sustain(656400,28). +sustain(656480,29). +sustain(656560,29). +sustain(656640,30). +sustain(656720,31). +sustain(656800,31). +sustain(656960,32). +sustain(657040,33). +sustain(657120,34). +sustain(657200,35). +sustain(657280,37). +sustain(657360,39). +sustain(657440,41). +sustain(657520,44). +sustain(657600,48). +sustain(657680,53). +sustain(657760,58). +sustain(657840,63). +sustain(657920,68). +sustain(658000,74). +sustain(658080,79). +sustain(658160,83). +sustain(658240,86). +sustain(658320,87). +sustain(658480,88). +sustain(658560,87). +sustain(658720,88). +sustain(658800,87). +sustain(659200,88). +sustain(659280,87). +sustain(659440,88). +sustain(659520,87). +sustain(659600,88). +sustain(659680,87). +sustain(660080,88). +sustain(660160,87). +sustain(660560,88). +sustain(660800,87). +sustain(660960,88). +sustain(661120,87). +sustain(661520,86). +sustain(661600,83). +sustain(661680,78). +sustain(661760,75). +sustain(661840,73). +sustain(661920,67). +sustain(662000,61). +sustain(662080,55). +sustain(662160,51). +sustain(662240,48). +sustain(662320,46). +sustain(662400,44). +sustain(662480,43). +sustain(663120,44). +sustain(663200,46). +sustain(663280,47). +sustain(663360,48). +sustain(663440,48). +sustain(663520,49). +sustain(663600,51). +sustain(663680,51). +sustain(663760,53). +sustain(663840,54). +sustain(663920,56). +sustain(664000,59). +sustain(664080,62). +sustain(664160,66). +sustain(664240,69). +sustain(664320,74). +sustain(664400,79). +sustain(664480,82). +sustain(664560,85). +sustain(664640,87). +sustain(664720,88). +sustain(664800,90). +sustain(664880,90). +sustain(669200,90). +sustain(669280,88). +sustain(669360,85). +sustain(669440,81). +sustain(669520,79). +sustain(669600,78). +sustain(669680,74). +sustain(669760,66). +sustain(669840,58). +sustain(669920,50). +sustain(670000,45). +sustain(670080,40). +sustain(670160,34). +sustain(670240,29). +sustain(670320,27). +sustain(670400,27). +sustain(670880,27). +sustain(670960,28). +sustain(671040,29). +sustain(671120,30). +sustain(671200,31). +sustain(671280,33). +sustain(671360,35). +sustain(671440,35). +sustain(671520,36). +sustain(671600,37). +sustain(671680,38). +sustain(671760,39). +sustain(671840,40). +sustain(671920,41). +sustain(672000,43). +sustain(672080,44). +sustain(672160,47). +sustain(672240,50). +sustain(672320,53). +sustain(672400,58). +sustain(672480,63). +sustain(672560,68). +sustain(672640,74). +sustain(672720,79). +sustain(672800,83). +sustain(672880,87). +sustain(672960,90). +sustain(673040,91). +sustain(673120,92). +sustain(673200,91). +sustain(673360,92). +sustain(673440,91). +sustain(673600,92). +sustain(673680,91). +sustain(673840,92). +sustain(673920,91). +sustain(674080,92). +sustain(674160,91). +sustain(674320,92). +sustain(674400,91). +sustain(674560,92). +sustain(674640,91). +sustain(674800,92). +sustain(674880,91). +sustain(675040,92). +sustain(675120,91). +sustain(675280,92). +sustain(675360,91). +sustain(675520,92). +sustain(675600,91). +sustain(675760,92). +sustain(675840,91). +sustain(676000,92). +sustain(676080,91). +sustain(676240,92). +sustain(676320,91). +sustain(676480,92). +sustain(676560,91). +sustain(676720,92). +sustain(676800,91). +sustain(676960,92). +sustain(677040,91). +sustain(677200,92). +sustain(677280,91). +sustain(677440,91). +sustain(677520,90). +sustain(677600,88). +sustain(677680,87). +sustain(677760,85). +sustain(677840,82). +sustain(677920,80). +sustain(678000,78). +sustain(678080,73). +sustain(678160,65). +sustain(678240,56). +sustain(678320,48). +sustain(678400,43). +sustain(678480,37). +sustain(678560,31). +sustain(678640,28). +sustain(678720,26). +sustain(679120,26). +sustain(679200,26). +sustain(679360,27). +sustain(679440,28). +sustain(679520,29). +sustain(679600,31). +sustain(679680,33). +sustain(679760,35). +sustain(679840,35). +sustain(679920,36). +sustain(680000,37). +sustain(680080,39). +sustain(680160,41). +sustain(680240,45). +sustain(680320,48). +sustain(680400,52). +sustain(680480,56). +sustain(680560,61). +sustain(680640,66). +sustain(680720,71). +sustain(680800,75). +sustain(680880,80). +sustain(680960,83). +sustain(681040,86). +sustain(681120,87). +sustain(681200,88). +sustain(682080,88). +sustain(682240,88). +sustain(682480,88). +sustain(682560,88). +sustain(682960,88). +sustain(683120,88). +sustain(683360,88). +sustain(683600,88). +sustain(683840,88). +sustain(683920,88). +sustain(684080,88). +sustain(684320,88). +sustain(684480,88). +sustain(684720,88). +sustain(684880,88). +sustain(684960,88). +sustain(685040,88). +sustain(685120,88). +sustain(685200,88). +sustain(685360,88). +sustain(685440,88). +sustain(685680,88). +sustain(685760,88). +sustain(685840,88). +sustain(685920,88). +sustain(686160,88). +sustain(686240,88). +sustain(686480,88). +sustain(686640,88). +sustain(686720,88). +sustain(686800,88). +sustain(687360,88). +sustain(687520,88). +sustain(687600,88). +sustain(687680,88). +sustain(688000,88). +sustain(688080,88). +sustain(688240,88). +sustain(688320,88). +sustain(688560,88). +sustain(688720,88). +sustain(688880,88). +sustain(689120,88). +sustain(689280,88). +sustain(689440,88). +sustain(689760,88). +sustain(690000,88). +sustain(690080,88). +sustain(690160,88). +sustain(690560,88). +sustain(690800,88). +sustain(691360,88). +sustain(691440,88). +sustain(691520,88). +sustain(691600,88). +sustain(691680,88). +sustain(691840,87). +sustain(691920,85). +sustain(692000,83). +sustain(692080,82). +sustain(692160,82). +sustain(692240,80). +sustain(692320,78). +sustain(692400,74). +sustain(692480,70). +sustain(692560,66). +sustain(692640,62). +sustain(692720,58). +sustain(692800,54). +sustain(692880,50). +sustain(692960,46). +sustain(693040,42). +sustain(693120,39). +sustain(693200,36). +sustain(693280,33). +sustain(693360,30). +sustain(693440,28). +sustain(693520,27). +sustain(693600,25). +sustain(693680,23). +sustain(693760,22). +sustain(693840,21). +sustain(693920,19). +sustain(694000,18). +sustain(694080,17). +sustain(694160,16). +sustain(694240,15). +sustain(694320,14). +sustain(694400,13). +sustain(694480,12). +sustain(694560,11). +sustain(694640,11). +sustain(694720,10). +sustain(694800,10). +sustain(701600,11). +sustain(701680,14). +sustain(701760,14). +sustain(701840,16). +sustain(701920,18). +sustain(702000,19). +sustain(702080,21). +sustain(702160,22). +sustain(702240,25). +sustain(702320,26). +sustain(702400,27). +sustain(702480,29). +sustain(702560,30). +sustain(702640,33). +sustain(702720,36). +sustain(702800,39). +sustain(702880,43). +sustain(702960,48). +sustain(703040,53). +sustain(703120,60). +sustain(703200,67). +sustain(703280,75). +sustain(703360,81). +sustain(703440,86). +sustain(703520,90). +sustain(703600,92). +sustain(703680,93). +sustain(708240,92). +sustain(708320,92). +sustain(708480,91). +sustain(708560,91). +sustain(708640,90). +sustain(708720,89). +sustain(708800,88). +sustain(708880,88). +sustain(708960,87). +sustain(709040,86). +sustain(709120,85). +sustain(709200,84). +sustain(709280,83). +sustain(709360,83). +sustain(709440,82). +sustain(709520,82). +sustain(709600,81). +sustain(709760,80). +sustain(709840,79). +sustain(709920,77). +sustain(710000,74). +sustain(710080,70). +sustain(710160,67). +sustain(710240,64). +sustain(710320,60). +sustain(710400,55). +sustain(710480,50). +sustain(710560,46). +sustain(710640,42). +sustain(710720,39). +sustain(710800,35). +sustain(710880,32). +sustain(710960,29). +sustain(711040,28). +sustain(711120,27). +sustain(711200,25). +sustain(711280,24). +sustain(711360,22). +sustain(711440,21). +sustain(711520,19). +sustain(711600,18). +sustain(711680,17). +sustain(711760,16). +sustain(711840,16). +sustain(712080,15). +sustain(712560,15). +sustain(715280,14). +sustain(717920,14). +sustain(720080,13). +sustain(721760,13). +sustain(731920,13). diff --git a/tests/test_performance_expressions.py b/tests/test_performance_expressions.py index e69de29b..51203409 100644 --- a/tests/test_performance_expressions.py +++ b/tests/test_performance_expressions.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This module contains test functions for Performance Array Calculations +""" +import unittest +import numpy as np +from partitura import load_match +from tests import MATCH_IMPORT_EXPORT_TESTFILES +from partitura.musicanalysis.performance_features import compute_performance_features +import os + + + +class TestPerformanceFeatures(unittest.TestCase): + def test_performance_features(self): + for fn in MATCH_IMPORT_EXPORT_TESTFILES: + print(fn) + perf, alignment, score = load_match(filename=fn, create_score=True) + features = compute_performance_features(score, + perf, + alignment, + feature_functions = "all") + + self.assertTrue(True, f"The expression features don't match the original.") From cc509cd1edf447747c010c7d5a401343c20dc9bf Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 16 May 2023 18:08:16 +0200 Subject: [PATCH 036/122] add normalization function for note_arrays --- partitura/utils/__init__.py | 2 + partitura/utils/normalize.py | 115 +++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 partitura/utils/normalize.py diff --git a/partitura/utils/__init__.py b/partitura/utils/__init__.py index 5f3ac483..1531eefb 100644 --- a/partitura/utils/__init__.py +++ b/partitura/utils/__init__.py @@ -61,6 +61,7 @@ deprecated_parameter, ) +from partitura.utils.normalize import normalize __all__ = [ "ensure_notearray", @@ -78,4 +79,5 @@ "show_diff", "PrettyPrintTree", "synthesize", + "normalize" ] diff --git a/partitura/utils/normalize.py b/partitura/utils/normalize.py new file mode 100644 index 00000000..30653319 --- /dev/null +++ b/partitura/utils/normalize.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This module contains normalization utilities +""" +import numpy as np + +DEFAULT_NORM_FUNCS = { + "FEATURE": {"func": lambda x: x / 127, # some normalization function + "kvargs": {}}, # some keyword arguments + # fill up with all note and performance features +} + +EPSILON=0.0001 + +def normalize(array, + norm_funcs = DEFAULT_NORM_FUNCS, + norm_func_fallback = minmaxrange_normalize, + default_value = -1.0): + + """ + Normalize a note array. + May include note features as well as performance features. + + Parameters + ---------- + array : np.ndarray + The performance array to be normalized. + norm_funcs : dict + A dictionary of normalization functions for each feature. + + Returns + ------- + array : np.ndarray + The normalized performance array. + """ + + for feature in array.dtype.names: + + # use mask for non-default values and don't change default values + non_default_mask = array[feature] != default_value + + # check whether the feature has non-uniform values + if len(np.unique(array[feature][non_default_mask])) == 1: + array[feature][non_default_mask] = 0.0 + else: + # check whether a normalization function is defined for the feature + if feature not in norm_funcs: + array[feature][non_default_mask] = norm_func_fallback(array[feature][non_default_mask]) + else: + array[feature][non_default_mask] = norm_funcs[feature]["func"](array[feature][non_default_mask], + **norm_funcs[feature]["kvargs"]) + + return array + + +def range_normalize(array, + min_value = None, + max_value = None, + log = False, + exp = False, + hard_clip = True): + + """ + Linear mapping a vector from range [min_value, max_value] to [0, 1]. + Preprocessing possible with log and exp. + Values exceeding the range [0, 1] are clipped to 0 or 1 if + clip is True, otherwise they are extrapolated. + """ + if min_value is None: + min_value = array.min() + if max_value is None: + max_value = array.max() + if log: + array = np.log(np.abs(array) + EPSILON) + if exp: + array = np.exp(array) + array = (array - min_value) / (max_value - min_value) + if hard_clip: + return np.clip(array, 0, 1) + else: + return array + + +def range_normalize(array, + min_value = -3.0, + max_value = 3.0, + log = False, + exp = False, + clip = True): + + """ + Compute zero mean and unit variance of a vector. + Preprocessing possible with log and exp. + Values exceeding the range [-min_value, max_value] + are clipped if clip is True. + """ + + if log: + array = np.log(np.abs(array) + EPSILON) + if exp: + array = np.exp(array) + + array = (array - array.mean()) / array.std() + if clip: + return np.clip(array, min_value, max_value) + else: + return array + + +def minmaxrange_normalize(array): + """ + Linear mapping a vector from range [min_value, max_value] to [0, 1]. + """ + return (array - array.min()) / (array.max() - array.min()) \ No newline at end of file From 8c9d1bdcf6cecdc82f09362df1b4610be9be520e Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Wed, 17 May 2023 11:13:58 +0200 Subject: [PATCH 037/122] pedal features added --- .../musicanalysis/performance_features.py | 125 ++++++++++-------- 1 file changed, 71 insertions(+), 54 deletions(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 8786043b..762ce162 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -14,9 +14,10 @@ import matplotlib.pyplot as plt from scipy import stats from scipy.optimize import least_squares +from scipy.signal import find_peaks import numpy.lib.recfunctions as rfn from partitura.score import ScoreLike -from partitura.performance import PerformanceLike +from partitura.performance import PerformanceLike, PerformedPart from partitura.utils.generic import interp1d from partitura.musicanalysis.performance_codec import to_matched_score, onsetwise_to_notewise, encode_tempo @@ -54,7 +55,7 @@ def compute_performance_features(score: ScoreLike, the functions themselves or the names of a feature function as strings (or a mix). currently implemented: - asynchrony_feature, articulation_feature, dynamics_feature + asynchrony_feature, articulation_feature, dynamics_feature, pedal_feature Returns ------- @@ -236,7 +237,7 @@ def parse_changing_ramp(unique_onset_idxs, m_score): ### Asynchrony -def asynchrony_feature(m_score: list, +def asynchrony_feature(m_score: np.ndarray, unique_onset_idxs: list, performance: PerformanceLike, v=False): @@ -245,10 +246,12 @@ def asynchrony_feature(m_score: list, Parameters ---------- - unique_onset_idxs : list - a list of arrays with the note indexes that have the same onset m_score : list correspondance between score and performance notes, with score markings. + unique_onset_idxs : list + a list of arrays with the note indexes that have the same onset + performance: PerformedPart + The original PerformedPart object Returns ------- @@ -293,7 +296,7 @@ def asynchrony_feature(m_score: list, ### Dynamics -def dynamics_feature(m_score : list, +def dynamics_feature(m_score : np.ndarray, unique_onset_idxs: list, performance: PerformanceLike, window : int = 3, @@ -303,8 +306,12 @@ def dynamics_feature(m_score : list, Parameters ---------- - m_score : structured array + m_score : list correspondance between score and performance notes, with score markings. + unique_onset_idxs : list + a list of arrays with the note indexes that have the same onset + performance: PerformedPart + The original PerformedPart object window : int Length of window to look at the range of dynamic marking coverage, default 3. agg : string @@ -378,7 +385,7 @@ def dynamics_feature(m_score : list, ### Articulation -def articulation_feature(m_score : list, +def articulation_feature(m_score : np.ndarray, unique_onset_idxs: list, performance: PerformanceLike, return_mask=False): @@ -390,8 +397,12 @@ def articulation_feature(m_score : list, Parameters ---------- - m_score : structured array + m_score : list correspondance between score and performance notes, with score markings. + unique_onset_idxs : list + a list of arrays with the note indexes that have the same onset + performance: PerformedPart + The original PerformedPart object return_mask : bool if true, return a boolean mask of legato notes, staccato notes and repeated notes @@ -473,30 +484,6 @@ def get_next_note(note_info, match_voiced): ### Pedals -def pedal_ramp(ppart: PerformedPart): - """Pedal ramp. - - Returns: - * pramp : a ramp function that ranges from 0 - to 127 with the change of sustain pedal - """ - pedal_controls = ppart.controls - W = np.zeros((len(onsets), 2)) - - for slur in slurs: - if not slur.end: - continue - x = [slur.start.t, slur.end.t] - y_inc = [0, 1] - y_dec = [1, 0] - W[:, 0] += interp1d(x, y_inc, bounds_error=False, fill_value=0)(onsets) - W[:, 1] += interp1d(x, y_dec, bounds_error=False, fill_value=0)(onsets) - - # Filter out NaN values - W[np.isnan(W)] = 0.0 - return W - - def pedal_feature(m_score : list, unique_onset_idxs: list, performance: PerformanceLike): @@ -505,39 +492,69 @@ def pedal_feature(m_score : list, Parameters ---------- - m_score : structured array + m_score : list correspondance between score and performance notes, with score markings. + unique_onset_idxs : list + a list of arrays with the note indexes that have the same onset + performance: PerformedPart + The original PerformedPart object Returns ------- pedal_ : structured array (4, n_notes) with fields - onset_value [0, 127]: - offset_value [0, 127]: - prev_release_time : - next_release_time: + onset_value [0, 127]: The interpolated pedal value at the onset + offset_value [0, 127]: The interpolated pedal value at the onset + to_prev_release [0, 10]: delta time from note onset to the previous pedal release 'peak' + to_next_release [0, 10]: delta time from note offset to the next pedal release 'peak' """ - pedal_ramp(performance.performedparts[0]) - hook() - kor_ = np.full(len(m_score), -1, dtype=[("kor", "f4")]) + + onset_offset_pedals, ramp_func = pedal_ramp(performance.performedparts[0], m_score) - # consider the note transition by each voice - for voice in np.unique(m_score['voice']): - match_voiced = m_score[m_score['voice'] == voice] - for _, note_info in enumerate(match_voiced): + x = np.linspace(0, 100, 200) + y = ramp_func(x) - if note_info['onset'] == match_voiced['onset'].max(): # last beat - break - next_note_info = get_next_note(note_info, match_voiced) # find most plausible transition + peaks, _ = find_peaks(-y, prominence=10) + peak_timepoints = x[peaks] - if next_note_info: # in some cases no meaningful transition - j = np.where(m_score == note_info)[0].item() # original position + release_times = np.zeros(len(m_score), dtype=[("to_prev_release", "f4"), ("to_next_release", "f4")]) + for i, note in enumerate(m_score): + peaks_before = peak_timepoints[note['p_onset'] >= peak_timepoints] + peaks_after = peak_timepoints[(note['p_onset'] + note['p_duration']) <= peak_timepoints] + if len(peaks_before): + release_times[i]["to_prev_release"] = min(note['p_onset'] - peaks_before.max(), 10) + if len(peaks_after): + release_times[i]["to_next_release"] = min(peaks_after.min() - (note['p_onset'] + note['p_duration']), 10) - if (note_info['offset'] == next_note_info['onset']): - kor_[j]['kor'] = get_kor(note_info, next_note_info) + # plt.plot(x[peaks], y[peaks], "x") + # plt.plot(x, y) + # plt.show() - + return rfn.merge_arrays([onset_offset_pedals, release_times], flatten=True, usemask=False) + +def pedal_ramp(ppart: PerformedPart, + m_score: np.ndarray): + """Pedal ramp in the same shape as the m_score. + + Returns: + * pramp : a ramp function that ranges from 0 + to 127 with the change of sustain pedal + """ + pedal_controls = ppart.controls + W = np.zeros((len(m_score), 2)) + onset_timepoints = m_score['p_onset'] + offset_timepoints = m_score['p_onset'] + m_score['p_duration'] + + timepoints = [control['time'] for control in pedal_controls] + values = [control['value'] for control in pedal_controls] + + agg_ramp_func = interp1d(timepoints, values, bounds_error=False, fill_value=0) + W[:, 0] = agg_ramp_func(onset_timepoints) + W[:, 1] = agg_ramp_func(offset_timepoints) + + # Filter out NaN values + W[np.isnan(W)] = 0.0 - return kor_ + return np.array([tuple(i) for i in W], dtype=[("onset_value", "f4"), ("offset_value", "f4")]), agg_ramp_func From e2585a97f682222875e7f3953996c8cb9e9ee4e4 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Wed, 17 May 2023 11:17:04 +0200 Subject: [PATCH 038/122] minor fix --- partitura/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/__init__.py b/partitura/__init__.py index f2080b2b..074b8726 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -27,7 +27,7 @@ from .musicanalysis import make_note_features, compute_note_array, full_note_array # define a version variable -# __version__ = pkg_resources.get_distribution("partitura").version +__version__ = pkg_resources.get_distribution("partitura").version #: An example MusicXML file for didactic purposes EXAMPLE_MUSICXML = pkg_resources.resource_filename( From 3822b3b13f0da99fe4cb3ba31d6197cb0665be5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Wed, 17 May 2023 18:27:58 +0200 Subject: [PATCH 039/122] fix issue 248 --- partitura/utils/music.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 40161d6a..98433a4c 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3398,7 +3398,7 @@ def slice_ppart_by_time( new_note = note.copy() new_note['note_on'] = 0. if clip_note_off: - new_note['note_off'] = min(note['note_off'] - start_time, end_time) + new_note['note_off'] = min(note['note_off'] - start_time, end_time - start_time) else: new_note['note_off'] = note['note_off'] - start_time if ppq: @@ -3414,7 +3414,7 @@ def slice_ppart_by_time( new_note = note.copy() new_note['note_on'] -= start_time if clip_note_off: - new_note['note_off'] = min(note['note_off'] - start_time, end_time) + new_note['note_off'] = min(note['note_off'] - start_time, end_time - start_time) else: new_note['note_off'] = note['note_off'] - start_time if ppq: From 95bbdbf4f279cfc4a4e12e55b48ec65fd16057c1 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 19 May 2023 14:58:39 +0200 Subject: [PATCH 040/122] fix bug in normalize --- .../musicanalysis/performance_features.py | 3 +- partitura/utils/normalize.py | 83 ++++++++++--------- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 129f9fce..ee952cad 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -487,7 +487,8 @@ def get_kor(e1, e2): return min(kor, 5) def get_next_note(note_info, match_voiced): - """get the next note in the same voice that's a resonalble transition + """ + get the next note in the same voice that's a reasonable transition note_info: the row of current note match_voiced: all notes in the same voice """ diff --git a/partitura/utils/normalize.py b/partitura/utils/normalize.py index 30653319..5a114915 100644 --- a/partitura/utils/normalize.py +++ b/partitura/utils/normalize.py @@ -13,46 +13,6 @@ EPSILON=0.0001 -def normalize(array, - norm_funcs = DEFAULT_NORM_FUNCS, - norm_func_fallback = minmaxrange_normalize, - default_value = -1.0): - - """ - Normalize a note array. - May include note features as well as performance features. - - Parameters - ---------- - array : np.ndarray - The performance array to be normalized. - norm_funcs : dict - A dictionary of normalization functions for each feature. - - Returns - ------- - array : np.ndarray - The normalized performance array. - """ - - for feature in array.dtype.names: - - # use mask for non-default values and don't change default values - non_default_mask = array[feature] != default_value - - # check whether the feature has non-uniform values - if len(np.unique(array[feature][non_default_mask])) == 1: - array[feature][non_default_mask] = 0.0 - else: - # check whether a normalization function is defined for the feature - if feature not in norm_funcs: - array[feature][non_default_mask] = norm_func_fallback(array[feature][non_default_mask]) - else: - array[feature][non_default_mask] = norm_funcs[feature]["func"](array[feature][non_default_mask], - **norm_funcs[feature]["kvargs"]) - - return array - def range_normalize(array, min_value = None, @@ -112,4 +72,45 @@ def minmaxrange_normalize(array): """ Linear mapping a vector from range [min_value, max_value] to [0, 1]. """ - return (array - array.min()) / (array.max() - array.min()) \ No newline at end of file + return (array - array.min()) / (array.max() - array.min()) + + +def normalize(array, + norm_funcs = DEFAULT_NORM_FUNCS, + norm_func_fallback = minmaxrange_normalize, + default_value = -1.0): + + """ + Normalize a note array. + May include note features as well as performance features. + + Parameters + ---------- + array : np.ndarray + The performance array to be normalized. + norm_funcs : dict + A dictionary of normalization functions for each feature. + + Returns + ------- + array : np.ndarray + The normalized performance array. + """ + + for feature in array.dtype.names: + + # use mask for non-default values and don't change default values + non_default_mask = array[feature] != default_value + + # check whether the feature has non-uniform values + if len(np.unique(array[feature][non_default_mask])) == 1: + array[feature][non_default_mask] = 0.0 + else: + # check whether a normalization function is defined for the feature + if feature not in norm_funcs: + array[feature][non_default_mask] = norm_func_fallback(array[feature][non_default_mask]) + else: + array[feature][non_default_mask] = norm_funcs[feature]["func"](array[feature][non_default_mask], + **norm_funcs[feature]["kvargs"]) + + return array From 92867c6893dcc2bbb110f1a23682eeb84493e0a6 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Fri, 19 May 2023 15:22:26 +0200 Subject: [PATCH 041/122] tmp save --- partitura/musicanalysis/performance_features.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 129f9fce..63d7beff 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -527,7 +527,7 @@ def pedal_feature(m_score : list, ------- pedal_ : structured array (4, n_notes) with fields onset_value [0, 127]: The interpolated pedal value at the onset - offset_value [0, 127]: The interpolated pedal value at the onset + offset_value [0, 127]: The interpolated pedal value at the offset to_prev_release [0, 10]: delta time from note onset to the previous pedal release 'peak' to_next_release [0, 10]: delta time from note offset to the next pedal release 'peak' """ @@ -669,3 +669,6 @@ def phrasing_attributes(m_score, unique_onset_idxs, plot=False): return phrasing_ + +### Tempo + From 0682a1dd2847a8339786be9817126f32463f3351 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Tue, 23 May 2023 12:13:00 +0200 Subject: [PATCH 042/122] fixed minor bugs in performance features after run-through of ASAP data: 1. Edge cases that generates nan in correlation features. 2. Onset boundary detection in incr-decr ramps. 3. Edge case for interp1d when there is no pedal controls. --- partitura/musicanalysis/performance_codec.py | 4 +- .../musicanalysis/performance_features.py | 131 +++++++++++------- 2 files changed, 85 insertions(+), 50 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 60fb887a..67aca073 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -289,6 +289,9 @@ def encode_articulation( sd[grace_mask] = 1 pd[grace_mask] = bp # Compute log articulation ratio + if (pd / (bp * sd))[0] <= 0: + pass + # warnings is given for this line if we have a negative beat_period - performed notes have reversed timing. articulation[idx] = np.log2(pd / (bp * sd)) return articulation @@ -634,7 +637,6 @@ def to_matched_score(score: ScoreLike, for a in alignment if (a["label"] == "match" and a["score_id"] in part_by_id) ] - ms = [] # sort according to onset (primary) and pitch (secondary) pitch_onset = [(sn['pitch'].item(), sn['onset_div'].item()) for sn, _ in note_pairs] diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index c97a66e3..fe6bc9df 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -98,7 +98,7 @@ def compute_performance_features(score: ScoreLike, np.where(np.logical_or(np.isnan(features_), np.isinf(features_)))[1] ) msg = "NaNs or Infs found in the following feature: {} ".format( - ", ".join(np.array(features.dtype)[problematic]) + ", ".join(np.array(features.dtype.names)[problematic]) ) raise InvalidPerformanceFeatureException(msg) @@ -228,38 +228,6 @@ def map_fields(note_info, fields): return np.array(["N/A"], dtype="U256") -def process_discrete_dynamics(constant_dyn): - """reverse the continuous dynamics ramp into discrete marks on the first beat. Rest of the events are filled with N/A""" - constant_dyn_shift = np.append(["N/A"], constant_dyn[:-1]) - positions = np.where(constant_dyn != constant_dyn_shift)[0] - - constant_dyn_ = np.full(len(constant_dyn), "N/A") - constant_dyn_[positions] = constant_dyn[positions] - - return constant_dyn_ - - -def parse_changing_ramp(unique_onset_idxs, m_score): - """parse the cresceando / decresceando ramp for the actively changing subsequences. - Return a list of (start, end) of the changing subsequences.""" - - unique_m_score = m_score[[idx[0] for idx in unique_onset_idxs]] - - increase = unique_m_score['loudness_direction_feature.loudness_incr'] - decrease = unique_m_score['loudness_direction_feature.loudness_decr'] - - onset_boundaries = [] - # finding the increase & decrease boundaries - for ramp in [increase, decrease]: - ramp_diff = np.append(ramp[1:], ramp[-1]) - ramp - has_ramp_diff = ramp_diff != 0 - ramp_boundary = np.append(has_ramp_diff[0], has_ramp_diff[:-1]) ^ has_ramp_diff - onset_boundary = unique_m_score[ramp_boundary]['onset'] - onset_boundaries.append([(onset_boundary[i], onset_boundary[i+1]) for i in range(0, len(onset_boundary), 2)]) - - return tuple(onset_boundaries), unique_m_score - - ### Asynchrony def asynchrony_feature(m_score: np.ndarray, unique_onset_idxs: list, @@ -282,8 +250,8 @@ def asynchrony_feature(m_score: np.ndarray, async_ : structured array structured array (broadcasted to the note level) with the following fields delta [0, 1]: the largest time difference (in seconds) between onsets in this group - pitch_cor [-1, 1]: correlation between timing and pitch - vel_cor [-1, 1]: correlation between timing and velocity + pitch_cor [-1, 1]: correlation between timing and pitch, min-scaling + vel_cor [-1, 1]: correlation between timing and velocity, min-scaling voice_std [0, 1]: std of the avg timing (in seconds) of each voice in this group """ @@ -300,14 +268,20 @@ def asynchrony_feature(m_score: np.ndarray, midi_pitch = note_group['pitch'] midi_pitch = midi_pitch - midi_pitch.min() # min-scaling onset_times = onset_times - onset_times.min() - cor = (-1) * np.corrcoef(midi_pitch, onset_times)[0, 1] + cor = (-1) * np.corrcoef(midi_pitch, onset_times)[0, 1] if ( + len(midi_pitch) > 1 and sum(midi_pitch) != 0 and sum(onset_times) != 0) else 0 # cor=nan if there is only one note in the group - async_[i]['pitch_cor'] = (0 if np.isnan(cor) else cor) + async_[i]['pitch_cor'] = cor + + assert not np.isnan(cor) midi_vel = note_group['velocity'].astype(float) midi_vel = midi_vel - midi_vel.min() - cor = (-1) * np.corrcoef(midi_vel, onset_times)[0, 1] - async_[i]['vel_cor'] = (0 if np.isnan(cor) else cor) + cor = (-1) * np.corrcoef(midi_vel, onset_times)[0, 1] if ( + sum(midi_vel) != 0 and sum(onset_times) != 0) else 0 + async_[i]['vel_cor'] = cor + + assert not np.isnan(cor) voices = np.unique(note_group['voice']) voices_onsets = [] @@ -361,6 +335,9 @@ def dynamics_feature(m_score : np.ndarray, constant_dynamics = list(zip(markings, velocity_agg)) + # only consider those in the OLS (there exist others like dolce, ) + constant_dynamics = [(m, v) for (m, v) in constant_dynamics if m in OLS] + if len(constant_dynamics) < 2: return dynamics_ @@ -392,21 +369,68 @@ def dynamics_feature(m_score : np.ndarray, for onset_boundaries, feat_name in zip([increase_ob, decrease_ob], ['loudness_direction_feature.loudness_incr', 'loudness_direction_feature.loudness_decr']): for start, end in onset_boundaries: - score_dynamics, performed_dyanmics, performed_bp = [], [], [] - for onset in unique_m_score[(unique_m_score['onset'] >= start) & (unique_m_score['onset'] <= end)]['onset']: + score_dynamics, performed_dyanmics = [], [] + notes_in_ramp = unique_m_score[(unique_m_score['onset'] >= start) & (unique_m_score['onset'] < end)] + for onset in notes_in_ramp['onset']: score_dynamics.append(unique_m_score[unique_m_score['onset'] == onset][0][feat_name]) - performed_dyanmics.append(unique_m_score[unique_m_score['onset'] == onset]['velocity'].mean()) - performed_bp.append(unique_m_score[unique_m_score['onset'] == onset]['beat_period'].item()) - cor = stats.pearsonr(score_dynamics, performed_dyanmics)[0] + performed_dyanmics.append(m_score[m_score['onset'] == onset]['velocity'].mean()) + + performed_bp = notes_in_ramp['beat_period'] + performed_bp = performed_bp - performed_bp.min() + performed_dyanmics = np.array(performed_dyanmics) + performed_dyanmics = performed_dyanmics - performed_dyanmics.min() + + cor = stats.pearsonr(score_dynamics, performed_dyanmics)[0] if ( + sum(performed_dyanmics) != 0 and sum(score_dynamics) != 0) else 0 if "decr" in feat_name: cor *= -1 ramp_mask = (m_score['onset'] >= start) & (m_score['onset'] <= end) dynamics_['ramp_cor'][ramp_mask] = cor - dynamics_['tempo_cor'][ramp_mask] = (-1) * stats.pearsonr(performed_bp, performed_dyanmics)[0] + dynamics_['tempo_cor'][ramp_mask] = (-1) * stats.pearsonr(performed_bp, performed_dyanmics)[0] if ( + sum(performed_bp) != 0 and sum(performed_dyanmics) != 0) else 0 return dynamics_ +def process_discrete_dynamics(constant_dyn): + """reverse the continuous dynamics ramp into discrete marks on the first beat. Rest of the events are filled with N/A""" + constant_dyn_shift = np.append(["N/A"], constant_dyn[:-1]) + positions = np.where(constant_dyn != constant_dyn_shift)[0] + + constant_dyn_ = np.full(len(constant_dyn), "N/A", dtype="U256") + constant_dyn_[positions] = constant_dyn[positions] + + return constant_dyn_ + + +def parse_changing_ramp(unique_onset_idxs, m_score): + """parse the cresceando / decresceando ramp for the actively changing subsequences. + Return a list of (start, end) of the changing subsequences.""" + + unique_m_score = m_score[[idx[0] for idx in unique_onset_idxs]] + + increase, decrease = np.zeros(unique_m_score.shape[0]), np.zeros(unique_m_score.shape[0]) + if 'loudness_direction_feature.loudness_incr' in unique_m_score.dtype.names: + increase = unique_m_score['loudness_direction_feature.loudness_incr'] + if 'loudness_direction_feature.loudness_decr' in unique_m_score.dtype.names: + decrease = unique_m_score['loudness_direction_feature.loudness_decr'] + + onset_boundaries = [] + # finding the increase & decrease boundaries + for ramp in [increase, decrease]: + ramp_diff = np.append(ramp[0], ramp[:-1]) - ramp + has_ramp_diff = ramp_diff != 0 + ramp_boundary = np.append(has_ramp_diff[0], has_ramp_diff[:-1]) ^ has_ramp_diff + onset_boundary = unique_m_score[ramp_boundary]['onset'] + + if len(onset_boundary) % 2 != 0: + onset_boundary = onset_boundary[:-1] + + onset_boundaries.append([(onset_boundary[i], onset_boundary[i+1]) for i in range(0, len(onset_boundary), 2)]) + + return tuple(onset_boundaries), unique_m_score + + ### Articulation def articulation_feature(m_score : np.ndarray, @@ -476,13 +500,18 @@ def articulation_feature(m_score : np.ndarray, def get_kor(e1, e2): - + """calculate the ratio between key overlap time and IOI. + In the case of a negative IOI (the order of notes in performance is reversed from the score), + set at default 0.""" + kot = e1['p_offset'] - e2['p_onset'] ioi = e2['p_onset'] - e1['p_onset'] + if ioi <= 0: + # warnings.warn(f"Getting KOR smaller than -1 in {e1['onset']}-{e1['pitch']} and {e2['onset']}-{e2['pitch']}.") + kor = 0 + kor = kot / ioi - if kor <= -1: - warnings.warn(f"Getting KOR smaller than -1 in {e1['onset']}-{e1['pitch']} and {e2['onset']}-{e2['pitch']}.") return min(kor, 5) @@ -528,9 +557,10 @@ def pedal_feature(m_score : list, ------- pedal_ : structured array (4, n_notes) with fields onset_value [0, 127]: The interpolated pedal value at the onset - offset_value [0, 127]: The interpolated pedal value at the offset + offset_value [0, 127]: The interpolated pedal value at the key offset to_prev_release [0, 10]: delta time from note onset to the previous pedal release 'peak' to_next_release [0, 10]: delta time from note offset to the next pedal release 'peak' + (think about something relates to the real duration) """ onset_offset_pedals, ramp_func = pedal_ramp(performance.performedparts[0], m_score) @@ -572,6 +602,9 @@ def pedal_ramp(ppart: PerformedPart, timepoints = [control['time'] for control in pedal_controls] values = [control['value'] for control in pedal_controls] + if len(timepoints) <= 1: # the case there is no pedal + timepoints, values = [0, 0], [0, 0] + agg_ramp_func = interp1d(timepoints, values, bounds_error=False, fill_value=0) W[:, 0] = agg_ramp_func(onset_timepoints) W[:, 1] = agg_ramp_func(offset_timepoints) From 04dce3c943718807bb13db523b85860c5661a56e Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Mon, 29 May 2023 14:02:51 +0100 Subject: [PATCH 043/122] fixed monotonize_times --- partitura/musicanalysis/performance_codec.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 67aca073..fcfe62b0 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -480,6 +480,7 @@ def tempo_by_average( s_iois = np.diff(unique_s_onsets_mt) beat_period = perf_iois / s_iois + hook() tempo_fun = interp1d( unique_s_onsets_mt[:-1], beat_period, @@ -906,12 +907,16 @@ def monotonize_times(s, deltas=None): if deltas is not None: _deltas = np.r_[np.min(deltas) - eps, deltas, np.max(deltas) + eps] else: - _deltas = None - mask = np.ones(_s.shape[0], dtype=bool) - mask[0] = mask[-1] = False + _deltas = None idx = np.arange(_s.shape[0]) + + # detect the position with value decrease and remove them from the interpolation values + mask = (_s[1:] - _s[:-1]) >= 0 + mask = np.r_[False, mask] + mask[-1] = False + s_mono = interp1d(idx[mask], _s[mask])(idx[1:-1]) - return _s[mask], _deltas[mask] + return s_mono, _deltas def notewise_to_onsetwise(notewise_inputs, unique_onset_idxs): From 64c97018ccd1ce8e9814fc3f8a217288b7e83bd0 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Mon, 29 May 2023 15:50:48 +0100 Subject: [PATCH 044/122] minor fixes --- partitura/musicanalysis/performance_codec.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index fcfe62b0..adda0f63 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -480,7 +480,6 @@ def tempo_by_average( s_iois = np.diff(unique_s_onsets_mt) beat_period = perf_iois / s_iois - hook() tempo_fun = interp1d( unique_s_onsets_mt[:-1], beat_period, @@ -911,12 +910,12 @@ def monotonize_times(s, deltas=None): idx = np.arange(_s.shape[0]) # detect the position with value decrease and remove them from the interpolation values - mask = (_s[1:] - _s[:-1]) >= 0 + mask = np.diff(_s) >= 0 mask = np.r_[False, mask] mask[-1] = False s_mono = interp1d(idx[mask], _s[mask])(idx[1:-1]) - return s_mono, _deltas + return s_mono, _deltas[1:-1] def notewise_to_onsetwise(notewise_inputs, unique_onset_idxs): From 1149a5022d9120775cf82f2b5f30bc8d44e81ae3 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 31 May 2023 10:22:59 +0200 Subject: [PATCH 045/122] added load_music21 in the general __init__ --- partitura/__init__.py | 1 + partitura/io/__init__.py | 1 + tests/__init__.py | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/partitura/__init__.py b/partitura/__init__.py index 074b8726..ba78139f 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -14,6 +14,7 @@ from .io.exportmusicxml import save_musicxml from .io.importmei import load_mei from .io.importkern import load_kern +from .io.importmusic21 import load_music21 from .io.importmidi import load_score_midi, load_performance_midi, midi_to_notearray from .io.exportmidi import save_score_midi, save_performance_midi from .io.importmatch import load_match diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index 2fd4e504..c142a681 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -13,6 +13,7 @@ from .importkern import load_kern from .importparangonada import load_parangonada_csv from .exportparangonada import save_parangonada_csv +from .importmusic21 import load_music21 from partitura.utils.misc import ( deprecated_alias, diff --git a/tests/__init__.py b/tests/__init__.py index b2a561e6..3947cc58 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -19,7 +19,6 @@ PARANGONADA_PATH = os.path.join(DATA_PATH, "parangonada") WAV_PATH = os.path.join(DATA_PATH, "wav") PNG_PATH = os.path.join(DATA_PATH, "png") -M21_PATH = DATA_PATH # this is a list of files for which importing and subsequent exporting should # yield identical MusicXML From 2ea5f147fd021d4c6bb01d270f502921822dd38b Mon Sep 17 00:00:00 2001 From: Patricia Hu Date: Thu, 1 Jun 2023 12:20:01 +0200 Subject: [PATCH 046/122] partial measures fix and test files --- .gitignore | 5 +- partitura/io/importmusicxml.py | 8 +- partitura/score.py | 4 + tests/__init__.py | 7 + .../musicxml/test_partial_measures.musicxml | 11740 ++++++++++++++++ .../test_partial_measures_consecutive.xml | 2889 ++++ tests/test_partial_measures.py | 77 + 7 files changed, 14727 insertions(+), 3 deletions(-) create mode 100644 tests/data/musicxml/test_partial_measures.musicxml create mode 100644 tests/data/musicxml/test_partial_measures_consecutive.xml create mode 100644 tests/test_partial_measures.py diff --git a/.gitignore b/.gitignore index 873f68d3..1b28c046 100644 --- a/.gitignore +++ b/.gitignore @@ -148,4 +148,7 @@ static *~ # vscode -.vscode \ No newline at end of file +.vscode + +# phdocs +phdocs.txt diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index c4bbd70a..2e485767 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -304,6 +304,7 @@ def load_musicxml( def _parse_parts(document, part_dict): + print('_parse_parts') # NOTE del """ Populate the Part instances that are the values of `part_dict` with the musical content in document. @@ -457,6 +458,7 @@ def _parse_parts(document, part_dict): def _handle_measure(measure_el, position, part, ongoing, doc_order): + # print('_handle_measure') # NOTE del """ Parse a ... element, adding it and its contents to the part. @@ -697,12 +699,14 @@ def _handle_new_system(position, part, ongoing): def make_measure(xml_measure): + # print('make_measure fn') # NOTE del later measure = score.Measure() # try: # measure.number = int(xml_measure.attrib['number']) # except: # LOGGER.warn('No number attribute found for measure') - measure.number = get_value_from_attribute(xml_measure, "number", int) + measure.number = get_value_from_attribute(xml_measure, "number", int) # TODO this isn't called + # measure.number = get_value_from_attribute(xml_measure, "number", str) # TODO return measure @@ -716,7 +720,7 @@ def _handle_attributes(e, position, part): fifths = get_value_from_tag(e, "key/fifths", int) mode = get_value_from_tag(e, "key/mode", str) - if fifths is not None or mode is not None: + if fifths is not None or mode is not jNone: part.add(score.KeySignature(fifths, mode), position) diat = get_value_from_tag(e, "transpose/diatonic", int) diff --git a/partitura/score.py b/partitura/score.py index 4d811346..22711a3e 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -270,6 +270,7 @@ def measure_map(self): @property def measure_number_map(self): + print("measure_number_map") # NOTE del """A function mapping timeline times to the measure number of the measure they are contained in. The function can take scalar values or lists/arrays of values. @@ -282,6 +283,9 @@ def measure_number_map(self): """ # operations to avoid None values and filter them efficiently. m_it = self.measures + print(type(m_it)) + print([m.number for m in self.measures]) + measures = np.array( [ [ diff --git a/tests/__init__.py b/tests/__init__.py index 3c313ee8..fa2e2ab2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -135,6 +135,13 @@ ] ] +MUSICXML_PARTIAL_MEASURES_TESTFILES = [ + os.path.join(MUSICXML_PATH, fn) + for fn in [ + "test_partial_measures.xml", + "test_partial_measures_consecutive.xml" + ] +] MEI_TESTFILES = [ os.path.join(MEI_PATH, fn) diff --git a/tests/data/musicxml/test_partial_measures.musicxml b/tests/data/musicxml/test_partial_measures.musicxml new file mode 100644 index 00000000..fbd93da5 --- /dev/null +++ b/tests/data/musicxml/test_partial_measures.musicxml @@ -0,0 +1,11740 @@ + + + + + K331 + Piano Sonata no. 11 in A major + + 1 + Andante grazioso + + Wolfgang Amadeus Mozart + + MuseScore 3.3.4 + 2021-04-27 + + + + + + + http://musescore.com/user/14709081/scores/4745241 + + + + 7.05556 + 40 + + + 1584 + 1224 + + 56.6929 + 56.6929 + 56.6929 + 113.386 + + + 56.6929 + 56.6929 + 56.6929 + 113.386 + + + + + + + Sonata No. XI in A Major + + + Wolfgang Amadeus Mozart (K. 331) + + + + Piano + Pno. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + + + 21.00 + -0.00 + + 141.15 + + + 80.00 + + + + heavy-light + + + + + + + + Var. V. + + 1 + + + + Adagio + + + + eighth + 60 + + + 1 + + + + + +

+ + + 1 + + + + + C + + none + + + + C + 1 + 5 + + 24 + 1 + eighth + down + 1 + begin + + + + C + 1 + 5 + + 36 + 1 + eighth + + down + 1 + continue + + + + + + + C + + none + + + + D + 5 + + 12 + 1 + 16th + down + 1 + end + backward hook + + + + + + + C + + none + + + + F + 1 + 5 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + E + 5 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + + 12 + 1 + 16th + 1 + + + + E + 5 + + 12 + 1 + 16th + down + 1 + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + 144 + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + B + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + + + + 21.00 + -0.00 + + 150.00 + + + 82.59 + + + + + C + + none + + + + C + 1 + 5 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + B + 4 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + + B + 4 + + 36 + 1 + eighth + + down + 1 + begin + + + + + + + C + + none + + + + C + 1 + 5 + + 12 + 1 + 16th + down + 1 + end + backward hook + + + + + + + C + + none + + + + E + 5 + + 24 + 1 + eighth + down + 1 + begin + + + + + + + C + + none + + + + D + 5 + + 12 + 1 + 16th + down + 1 + end + backward hook + + + + + + + 6 + 1 + 32nd + 1 + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + + + + C + + none + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + 144 + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + sharp + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + A + 1 + 3 + + 6 + 5 + 32nd + sharp + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + sharp + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + B + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + + + + 21.00 + 0.00 + + 150.00 + + + 80.00 + + + + + C + + none + + + + B + 4 + + 12 + 1 + 16th + up + 1 + begin + begin + + + + + + + A + 4 + + 12 + 1 + 16th + up + 1 + end + end + + + + + + + 6 + 1 + 32nd + 1 + + + + A + 4 + + 6 + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + G + 1 + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + continue + + + + A + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + begin + begin + begin + + + + A + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + natural + up + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + + + + C + + none + + + + A + 1 + 4 + + 12 + 1 + 16th + sharp + up + 1 + begin + begin + + + + + + + B + 4 + + 12 + 1 + 16th + up + 1 + end + end + + + + + + + 6 + 1 + 32nd + 1 + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + A + 1 + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + 144 + + + + F + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + sharp + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + F + 1 + 3 + + 24 + 5 + eighth + down + 2 + + + + 24 + 5 + eighth + 2 + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + sharp + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + G + 1 + 3 + + 24 + 5 + eighth + down + 2 + + + + 24 + 5 + eighth + 2 + + + + + + + 21.00 + 0.00 + + 150.00 + + + 80.00 + + + + + C + + none + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 1 + 4 + + 6 + 1 + 32nd + sharp + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + C + + none + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 1 + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + C + + none + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + C + 1 + 5 + + 6 + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + B + 4 + + 6 + 1 + 32nd + natural + up + 1 + continue + continue + continue + + + + A + 1 + 4 + + 6 + 1 + 32nd + sharp + up + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + A + 4 + + 6 + 1 + 32nd + natural + up + 1 + begin + begin + begin + + + + G + 1 + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + continue + + + + F + 2 + 4 + + 6 + 1 + 32nd + double-sharp + up + 1 + continue + continue + continue + + + + G + 1 + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + F + 1 + 4 + + 6 + 1 + 32nd + sharp + up + 1 + begin + begin + begin + + + + E + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 1 + 32nd + sharp + up + 1 + continue + continue + continue + + + + E + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + + + 144 + + + + A + 3 + + 24 + 5 + eighth + down + 2 + begin + + + + + E + 4 + + 24 + 5 + eighth + down + 2 + + + + F + 1 + 3 + + 24 + 5 + eighth + down + 2 + continue + + + + + A + 3 + + 24 + 5 + eighth + down + 2 + + + + D + 3 + + 24 + 5 + eighth + down + 2 + end + + + + + B + 3 + + 24 + 5 + eighth + down + 2 + + + + E + 3 + + 24 + 5 + eighth + down + 2 + + + + + G + 1 + 3 + + 24 + 5 + eighth + down + 2 + + + + 24 + 5 + eighth + 2 + + + + 24 + 5 + eighth + 2 + + + + + + + 21.00 + 0.00 + + 70.00 + + + 82.79 + + + + + + + + + 1 + + + + + C + + none + + + + A + 4 + + 12 + 1 + 16th + down + 1 + begin + forward hook + + + + + C + 1 + 5 + + 12 + 1 + 16th + down + 1 + + + + A + 4 + + 24 + 1 + eighth + down + 1 + continue + + + + + C + 1 + 5 + + 24 + 1 + eighth + down + 1 + + + + A + 1 + 4 + + 12 + 1 + 16th + sharp + down + 1 + continue + begin + + + + + + + + C + 1 + 5 + + 12 + 1 + 16th + down + 1 + + + + B + 4 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + D + 5 + + 12 + 1 + 16th + down + 1 + + + + B + 1 + 4 + + 12 + 1 + 16th + sharp + down + 1 + end + end + + + + + D + 1 + 5 + + 12 + 1 + 16th + sharp + down + 1 + + + + C + + none + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + + + + A + 4 + + 6 + 1 + 32nd + natural + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + natural + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + natural + down + 1 + continue + continue + continue + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + G + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + B + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 6 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + 6 + 1 + 32nd + 1 + + + 144 + + + + A + 2 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + +

+ + + 2 + + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 24 + 5 + eighth + down + 2 + + + + 24 + 5 + eighth + 2 + + + + 24 + 5 + eighth + 2 + + + + + + + 21.00 + -0.00 + + 150.00 + + + 80.00 + + + + + + + + + 1 + + + + + C + + none + + + + A + 4 + + 6 + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + C + 1 + 5 + + 6 + 1 + 32nd + up + 1 + + + + G + 1 + 4 + + 6 + 1 + 32nd + up + 1 + continue + end + end + + + + + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + + + + G + 1 + 4 + + 24 + 1 + eighth + up + 1 + continue + + + + + B + 4 + + 24 + 1 + eighth + up + 1 + + + + +

+ + + 1 + + + + + G + 1 + 4 + + 12 + 1 + 16th + up + 1 + continue + begin + + + + + + + + B + 4 + + 12 + 1 + 16th + up + 1 + + + + A + 4 + + 12 + 1 + 16th + up + 1 + continue + continue + + + + + C + 5 + + 12 + 1 + 16th + natural + up + 1 + + + + A + 1 + 4 + + 12 + 1 + 16th + sharp + up + 1 + end + end + + + + + C + 1 + 5 + + 12 + 1 + 16th + sharp + up + 1 + + + + C + + none + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + G + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + natural + down + 1 + end + end + end + + + + B + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + C + 1 + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 6 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + 6 + 1 + 32nd + 1 + + + 144 + + + + E + 2 + + 6 + 5 + 32nd + up + 2 + begin + begin + begin + + + + + + + E + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + + + + E + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + up + 2 + end + end + end + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + E + 3 + + 24 + 5 + eighth + down + 2 + + + + 24 + 5 + eighth + 2 + + + + 24 + 5 + eighth + 2 + + + + + + + 21.00 + -0.00 + + 150.00 + + + 80.00 + + + + + C + + none + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + A + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + end + + + + + + + A + 4 + + 12 + + 1 + 16th + up + 1 + end + end + + + + + + + A + 4 + + 6 + + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + A + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + C + + none + + + + A + 1 + 4 + + 6 + 1 + 32nd + sharp + down + 1 + begin + begin + begin + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + B + 1 + 4 + + 6 + 1 + 32nd + sharp + down + 1 + begin + begin + begin + + + + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + A + 5 + + 6 + 1 + 32nd + natural + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + C + + none + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + natural + down + 1 + end + end + end + + + + + + 144 + + + + 24 + 5 + eighth + 2 + + + + F + 1 + 3 + + 24 + 5 + eighth + down + 2 + begin + + + + + E + 4 + + 24 + 5 + eighth + down + 2 + + + + G + 1 + 3 + + 24 + 5 + eighth + down + 2 + end + + + + + E + 4 + + 24 + 5 + eighth + down + 2 + + + + A + 3 + + 24 + 5 + eighth + down + 2 + + + + + E + 4 + + 24 + 5 + eighth + down + 2 + + + + 24 + 5 + eighth + 2 + + + + D + 3 + + 24 + 5 + eighth + down + 2 + + + + + F + 1 + 3 + + 24 + 5 + eighth + down + 2 + + + + + B + 3 + + 24 + 5 + eighth + down + 2 + + + + + + + 21.00 + -0.00 + + 150.00 + + + 80.00 + + + + + + + + C + + none + + + + E + 5 + + 18 + 1 + 16th + + down + 1 + begin + begin + + + + + + + + F + 1 + 5 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + E + 5 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + D + 1 + 5 + + 1 + 32nd + sharp + up + 1 + continue + continue + continue + + + + + E + 5 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + backward hook + + + + + + + C + + none + + + + A + 4 + + 36 + 1 + eighth + + up + 1 + begin + + + + + + + C + + none + + + + C + 1 + 5 + + 6 + 1 + 32nd + up + 1 + continue + begin + begin + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + + + + C + + none + + + + G + 1 + 4 + + 24 + 1 + eighth + up + 1 + begin + + + + + + + + B + 4 + + 24 + 1 + eighth + up + 1 + + + + C + + none + + + + + + + + A + 4 + + 12 + 1 + 16th + up + 1 + + + + C + 4 + + 9 + 1 + 32nd + + 1 + + + + E + 4 + + 3 + 1 + 64th + up + 1 + end + end + backward hook + backward hook + + + + F + 1 + 4 + + 3 + 1 + 64th + up + 1 + begin + begin + begin + begin + + + + + + + A + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + G + 1 + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + B + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + A + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + C + 1 + 5 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + B + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + D + 5 + + 3 + 1 + 64th + natural + up + 1 + end + end + end + end + + + + + + + + 96 + + + + + A + 4 + + 36 + 2 + eighth + + down + 1 + begin + + + + G + 1 + 4 + + 12 + 2 + 16th + down + 1 + end + backward hook + + + + E + 4 + + 48 + 2 + quarter + down + 1 + + + 120 + + + + A + 3 + + 48 + 5 + quarter + up + 2 + + + + + C + 1 + 4 + + 48 + 5 + quarter + up + 2 + + + + B + 3 + + 24 + 5 + eighth + up + 2 + + + + + D + 4 + + 24 + 5 + eighth + up + 2 + + + + D + 4 + + 24 + 5 + eighth + up + 2 + begin + + + + + + + C + 1 + 4 + + 24 + 5 + eighth + up + 2 + end + + + + + + + G + 2 + + 24 + 5 + eighth + 2 + + + 144 + + + + E + 3 + + 72 + 6 + quarter + + down + 2 + + + + A + 3 + + 48 + 6 + quarter + down + 2 + + + 24 + + + light-heavy + + + + + + + + + 21.00 + -0.00 + + 110.60 + + + 80.00 + + + + + + + + C + + none + + + + E + 5 + + 18 + 1 + 16th + + down + 1 + begin + begin + + + + + + + + F + 1 + 5 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + E + 5 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + D + 1 + 5 + + 1 + 32nd + sharp + up + 1 + continue + continue + continue + + + + + E + 5 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + backward hook + + + + + + + C + + none + + + + A + 4 + + 36 + 1 + eighth + + up + 1 + begin + + + + + + + C + + none + + + + C + 1 + 5 + + 6 + 1 + 32nd + up + 1 + continue + begin + begin + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + + + + C + + none + + + + + E + 4 + + 24 + 1 + eighth + up + 1 + begin + + + + + + + + G + 1 + 4 + + 24 + 1 + eighth + up + 1 + + + + + B + 4 + + 24 + 1 + eighth + up + 1 + + + + C + + none + + + + A + 4 + + 18 + 1 + 16th + + up + 1 + continue + begin + + + + + + + + B + 4 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + A + 4 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + G + 1 + 4 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + A + 4 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + forward hook + + + + C + 1 + 5 + + 18 + 1 + 16th + + up + 1 + continue + continue + + + + + D + 5 + + 1 + 32nd + natural + up + 1 + begin + begin + begin + + + + + + + + C + 1 + 5 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + B + 4 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + C + 1 + 5 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + D + 5 + + 6 + 1 + 32nd + up + 1 + end + end + backward hook + + + 144 + + + + + 96 + + + + A + 4 + + 36 + 2 + eighth + + down + 1 + begin + + + + G + 1 + 4 + + 12 + 2 + 16th + down + 1 + end + backward hook + + + 72 + + + + A + 3 + + 48 + 5 + quarter + up + 2 + + + + + C + 1 + 4 + + 48 + 5 + quarter + up + 2 + + + + B + 3 + + 24 + 5 + eighth + up + 2 + + + + + D + 4 + + 24 + 5 + eighth + up + 2 + + + + D + 4 + + 24 + 5 + eighth + up + 2 + begin + + + + + + + C + 1 + 4 + + 24 + 5 + eighth + up + 2 + end + + + + + + + 24 + 5 + eighth + 2 + + + 144 + + + + E + 3 + + 72 + 6 + quarter + + down + 2 + + + + A + 3 + + 48 + 6 + quarter + down + 2 + + + 24 + + + light-heavy + + + + + + + + 21.00 + -0.00 + + 150.00 + + + 80.00 + + + + heavy-light + + + + + G + 2 + + + + + C + + none + + + + E + 5 + + 18 + 1 + 16th + + down + 1 + begin + begin + + + + + + + F + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + begin + begin + + + + E + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + end + end + + + + C + + none + + + + D + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + C + 1 + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + C + + none + + + + B + 4 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + A + 4 + + 12 + 1 + 16th + down + 1 + end + end + + + + C + + none + + + + A + 4 + + 18 + 1 + 16th + + down + 1 + begin + begin + + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + end + forward hook + + + + + + + C + + none + + + + F + 1 + 5 + + 24 + 1 + eighth + down + 1 + end + + + + C + + none + + + + D + 4 + + 24 + + 1 + eighth + up + 1 + + + + + + 144 + + + + A + 3 + + 6 + 5 + 32nd + up + 2 + begin + begin + begin + + + + + + + A + 4 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + C + 1 + 4 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + A + 4 + + 6 + 5 + 32nd + up + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + up + 2 + begin + begin + begin + + + + G + 1 + 4 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + B + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + G + 1 + 4 + + 6 + 5 + 32nd + up + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + up + 2 + begin + begin + begin + + + + G + 4 + + 6 + 5 + 32nd + natural + up + 2 + continue + continue + continue + + + + C + 1 + 4 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + G + 4 + + 6 + 5 + 32nd + up + 2 + end + end + end + + + + + + + A + 3 + + 6 + 5 + 32nd + up + 2 + begin + begin + begin + + + + + + + F + 1 + 4 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + D + 4 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + F + 1 + 4 + + 6 + 5 + 32nd + up + 2 + end + end + end + + + + + + + F + 4 + + + + + simile + + 2 + + + + F + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + D + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + D + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + F + 1 + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + 21.00 + 0.00 + + 150.00 + + + 96.79 + + + + + C + + none + + + + D + 4 + + 3 + + 1 + 64th + up + 1 + begin + begin + begin + begin + + + + + + + + F + 1 + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + E + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + D + 4 + + 3 + 1 + 64th + up + 1 + end + end + end + end + + + + E + 4 + + 3 + 1 + 64th + up + 1 + begin + begin + begin + begin + + + + F + 1 + 4 + + 3 + 1 + 64th + up + 1 + continue + continue + continue + continue + + + + G + 4 + + 3 + 1 + 64th + natural + up + 1 + continue + continue + continue + continue + + + + A + 4 + + 3 + 1 + 64th + up + 1 + end + end + end + end + + + + B + 4 + + 3 + 1 + 64th + down + 1 + begin + begin + begin + begin + + + + C + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + D + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + E + 5 + + 3 + 1 + 64th + down + 1 + end + end + end + end + + + + F + 1 + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + begin + begin + begin + + + + + + + + G + 5 + + 4 + 1 + 32nd + natural + + 3 + 2 + + down + 1 + continue + continue + continue + + + + A + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + end + end + end + + + + + + + B + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + C + 1 + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 6 + + 9 + 1 + 32nd + + down + 1 + continue + continue + continue + + + + F + 1 + 5 + + 3 + 1 + 64th + down + 1 + end + end + end + backward hook + + + + A + 5 + + 18 + 1 + 16th + + down + 1 + begin + begin + + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + forward hook + + + + + + + C + + none + + + + E + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + E + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + E + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + E + 5 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + 144 + + + + A + 2 + + 6 + 5 + 32nd + up + 2 + begin + begin + begin + + + + F + 1 + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + D + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + F + 1 + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + A + 2 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + F + 1 + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + D + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + F + 1 + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + A + 2 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + F + 1 + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + D + 3 + + 6 + 5 + 32nd + up + 2 + continue + continue + continue + + + + F + 1 + 3 + + 6 + 5 + 32nd + up + 2 + end + end + end + + + + A + 2 + + 24 + 5 + eighth + up + 2 + begin + + + + + + + + D + 3 + + 24 + 5 + eighth + up + 2 + + + + A + 2 + + 24 + 5 + eighth + up + 2 + end + + + + + + + + C + 1 + 3 + + 24 + 5 + eighth + up + 2 + + + + 24 + 5 + eighth + 2 + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + + + + + 1 + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + A + 4 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + E + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + E + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + E + 5 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + + C + + none + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + end + + + + G + 1 + 4 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + E + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + E + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + E + 5 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + 144 + + + + A + 3 + + 72 + 5 + quarter + + down + 2 + + + + + C + 1 + 4 + + 72 + 5 + quarter + + down + 2 + + + + + E + 4 + + 72 + 5 + quarter + + down + 2 + + + + + + + + 2 + + + + + B + 3 + + 72 + 5 + quarter + + down + 2 + + + + + D + 4 + + 72 + 5 + quarter + + down + 2 + + + + + E + 4 + + 72 + 5 + quarter + + down + 2 + + + + + + + 21.00 + -0.00 + + 150.00 + + + 86.86 + + + + + C + + none + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + C + 1 + 5 + + 24 + 1 + eighth + up + 1 + begin + + + + + + + C + + none + + + + B + 4 + + 6 + 1 + 32nd + up + 1 + continue + begin + begin + + + + + + + E + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + continue + + + + + + + E + 5 + + 6 + 1 + 32nd + up + 1 + continue + continue + continue + + + + E + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + D + 1 + 5 + + 6 + 1 + 32nd + sharp + up + 1 + begin + begin + begin + + + + E + 4 + + 6 + 1 + 32nd + up + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + natural + up + 1 + continue + continue + continue + + + + E + 4 + + 6 + 1 + 32nd + up + 1 + end + end + end + + + + + + 144 + + + + + + + + 2 + + + + + A + 3 + + 24 + 5 + eighth + down + 2 + begin + + + + + C + 1 + 4 + + 24 + 5 + eighth + down + 2 + + + + + E + 4 + + 24 + 5 + eighth + down + 2 + + + + + + + + 2 + + + + + G + 1 + 3 + + 24 + 5 + eighth + down + 2 + continue + + + + + B + 3 + + 24 + 5 + eighth + down + 2 + + + + + E + 4 + + 24 + 5 + eighth + down + 2 + + + + + + + + 2 + + + + + A + 3 + + 24 + 5 + eighth + down + 2 + end + + + + + C + 1 + 4 + + 24 + 5 + eighth + down + 2 + + + + + E + 4 + + 24 + 5 + eighth + down + 2 + + + + A + 3 + + 24 + 5 + eighth + up + 2 + begin + + + + + + + + C + 1 + 4 + + 24 + 5 + eighth + up + 2 + + + + G + 1 + 3 + + 24 + 5 + eighth + up + 2 + end + + + + + + + + B + 3 + + 24 + 5 + eighth + up + 2 + + + + 24 + 5 + eighth + 2 + + + 144 + + + 72 + + + + E + 3 + + 48 + 6 + quarter + down + 2 + + + 24 + + + + + + + 21.00 + 0.00 + + 70.00 + + + 80.00 + + + + + C + + none + + + + C + 1 + 5 + + 24 + 1 + eighth + down + 1 + begin + + + + C + 1 + 5 + + 36 + 1 + eighth + + down + 1 + continue + + + + + + + C + + none + + + + + D + 5 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + C + 1 + 5 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + B + 4 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + C + 1 + 5 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + D + 5 + + 12 + 1 + 16th + down + 1 + end + backward hook + + + + + + + C + + none + + + + F + 1 + 5 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + end + end + backward hook + + + + + + + 6 + 1 + 32nd + 1 + + + + D + 1 + 6 + + 12 + 1 + 16th + sharp + down + 1 + begin + begin + + + + + + + E + 6 + + 6 + 1 + 32nd + down + 1 + end + end + backward hook + + + + + + + 3 + 1 + 64th + 1 + + + + E + 5 + + 3 + 1 + 64th + down + 1 + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + 144 + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + B + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + C + 1 + 4 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + + + + 21.00 + -0.00 + + 150.00 + + + 99.20 + + + + + C + + none + + + + C + 1 + 5 + + 12 + 1 + 16th + down + 1 + begin + forward hook + + + + + + + B + 4 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + B + 4 + + 24 + 1 + eighth + down + 1 + continue + + + + C + + none + + + + + C + 1 + 5 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + B + 4 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + A + 1 + 4 + + 1 + 32nd + sharp + up + 1 + end + end + end + + + + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + continue + begin + begin + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + C + + none + + + + E + 5 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + end + end + backward hook + + + + + + + 6 + 1 + 32nd + 1 + + + + C + + none + + + + C + 1 + 6 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + D + 6 + + 6 + 1 + 32nd + down + 1 + end + end + backward hook + + + + + + + 3 + 1 + 64th + 1 + + + + D + 5 + + 3 + 1 + 64th + down + 1 + + + + C + + none + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + 144 + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + sharp + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + B + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + sharp + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + B + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + + + + 21.00 + -0.00 + + 150.00 + + + 106.07 + + + + + C + + none + + + + B + 4 + + 12 + 1 + 16th + up + 1 + begin + begin + + + + + + + A + 4 + + 12 + + 1 + 16th + up + 1 + end + end + + + + + + + + A + 4 + + 6 + + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + A + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + D + 5 + + 6 + 1 + 32nd + natural + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + A + 1 + 4 + + 6 + 1 + 32nd + sharp + down + 1 + begin + begin + begin + + + + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + B + 1 + 4 + + 6 + 1 + 32nd + sharp + down + 1 + begin + begin + begin + + + + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + natural + down + 1 + continue + continue + continue + + + + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + natural + down + 1 + end + end + end + + + + + + 144 + + + + F + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + sharp + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + F + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + D + 1 + 4 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 4 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + + + + A + 3 + + 24 + 5 + eighth + down + 2 + + + + 24 + 5 + eighth + 2 + + + + D + 3 + + 24 + 5 + eighth + natural + down + 2 + + + + + F + 1 + 3 + + 24 + 5 + eighth + down + 2 + + + + + B + 3 + + 24 + 5 + eighth + natural + down + 2 + + + + + + + 21.00 + -0.00 + + 150.00 + + + 101.86 + + + + + C + + none + + + + E + 5 + + 18 + 1 + 16th + + down + 1 + begin + begin + + + + + + + + F + 1 + 5 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + E + 5 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + D + 1 + 5 + + 1 + 32nd + sharp + up + 1 + continue + continue + continue + + + + + E + 5 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + F + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + backward hook + + + + + + + C + + none + + + + A + 4 + + 36 + 1 + eighth + + up + 1 + begin + + + + + + + C + + none + + + + B + 4 + + 12 + 1 + 16th + up + 1 + end + backward hook + + + + + + + C + + none + + + + + + + + 1 + + + + + B + 4 + + 36 + 1 + eighth + + up + 1 + begin + + + + + + + B + 1 + 4 + + 12 + 1 + 16th + sharp + up + 1 + continue + forward hook + + + + C + + none + + + + C + 1 + 5 + + 24 + 1 + eighth + up + 1 + end + + + + + + 144 + + + 72 + + + + G + 1 + 4 + + 48 + 2 + quarter + down + 1 + + + + + + + A + 4 + + 24 + 2 + eighth + down + 1 + + + + + + 144 + + + + A + 3 + + 48 + 5 + quarter + up + 2 + + + + + C + 1 + 4 + + 48 + 5 + quarter + up + 2 + + + + B + 3 + + 24 + 5 + eighth + up + 2 + + + + + D + 4 + + 24 + 5 + eighth + up + 2 + + + + D + 4 + + 48 + 5 + quarter + up + 2 + + + + + + + C + 1 + 4 + + 24 + 5 + eighth + up + 2 + + + + + + 144 + + + + E + 3 + + 72 + 6 + quarter + + down + 2 + + + + A + 3 + + 72 + 6 + quarter + + down + 2 + + + + + E + 4 + + 72 + 6 + quarter + + down + 2 + + + + + + + 21.00 + 0.00 + + 91.47 + + + 80.11 + + + + + C + + none + + + + + + + + 1 + + + + + + E + 4 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + A + 4 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + C + 1 + 5 + + 24 + + 1 + eighth + down + 1 + begin + + + + + + + C + 1 + 5 + + 4 + + 1 + 32nd + + 3 + 2 + + down + 1 + continue + begin + begin + + + + + + + + E + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + continue + continue + continue + + + + + + + D + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + end + end + end + + + + + + + C + 1 + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + begin + begin + begin + + + + + + + D + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + continue + continue + continue + + + + E + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + end + end + end + + + + + + + C + + none + + + + D + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + begin + begin + begin + + + + + + + F + 1 + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + continue + continue + continue + + + + E + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + end + end + end + + + + + + + D + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + begin + begin + begin + + + + + + + E + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + continue + continue + continue + + + + F + 1 + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + end + end + end + + + + + + + + C + + none + + + + E + 5 + + 3 + 1 + 64th + down + 1 + begin + begin + begin + begin + + + + A + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + + + + B + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + C + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + D + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + E + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + F + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + A + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + B + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + C + 1 + 6 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + B + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + A + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + F + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + E + 1 + 5 + + 3 + 1 + 64th + sharp + down + 1 + continue + continue + continue + continue + + + + C + + none + + + + F + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + A + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + B + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + A + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + F + 1 + 5 + + 3 + 1 + 64th + down + 1 + end + end + end + end + + + 144 + + + + A + 2 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + + + + C + 1 + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 2 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + C + 1 + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + E + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + A + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + B + 2 + + 6 + 5 + 32nd + down + 2 + begin + begin + begin + + + + D + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + G + 1 + 3 + + 6 + 5 + 32nd + down + 2 + continue + continue + continue + + + + B + 3 + + 6 + 5 + 32nd + down + 2 + end + end + end + + + + A + 3 + + 72 + 5 + quarter + + up + 2 + + + + + + 144 + + + 72 + + + + C + 1 + 3 + + 48 + 6 + quarter + down + 2 + + + + D + 3 + + 24 + 6 + eighth + down + 2 + + + + + + + 21.00 + -0.00 + + 150.00 + + + 80.00 + + + + + + + + +

+ + + 1 + + + + + C + + none + + + + E + 5 + + 6 + 1 + 32nd + natural + down + 1 + begin + begin + begin + + + + + + + E + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + C + 1 + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + C + + none + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + B + 4 + + 24 + 1 + eighth + up + 1 + begin + + + + + + + C + + none + + + + A + 4 + + 4 + 1 + 32nd + + 3 + 2 + + up + 1 + continue + begin + begin + + + + + + + + E + 4 + + 4 + 1 + 32nd + + 3 + 2 + + up + 1 + continue + continue + continue + + + + F + 1 + 4 + + 4 + 1 + 32nd + + 3 + 2 + + up + 1 + end + end + end + + + + + + + G + 4 + + 4 + 1 + 32nd + natural + + 3 + 2 + + up + 1 + begin + begin + begin + + + + + + + G + 1 + 4 + + 4 + 1 + 32nd + sharp + + 3 + 2 + + up + 1 + continue + continue + continue + + + + A + 4 + + 4 + 1 + 32nd + + 3 + 2 + + up + 1 + end + end + end + + + + + + + A + 1 + 4 + + 4 + 1 + 32nd + sharp + + 3 + 2 + + up + 1 + begin + begin + begin + + + + + + + B + 4 + + 4 + 1 + 32nd + + 3 + 2 + + up + 1 + continue + continue + continue + + + + B + 1 + 4 + + 4 + 1 + 32nd + sharp + + 3 + 2 + + up + 1 + end + end + end + + + + + + + C + 1 + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + begin + begin + begin + + + + + + + D + 5 + + 4 + 1 + 32nd + + 3 + 2 + + down + 1 + continue + continue + continue + + + + D + 1 + 5 + + 4 + 1 + 32nd + sharp + + 3 + 2 + + down + 1 + end + end + end + + + + + + 144 + + + 72 + + + + G + 1 + 4 + + 24 + 2 + eighth + down + 1 + + + 96 + + + + E + 3 + + 24 + 5 + eighth + down + 2 + begin + + + + + C + 1 + 4 + + 24 + 5 + eighth + down + 2 + + + + E + 3 + + 24 + 5 + eighth + down + 2 + continue + + + + + C + 1 + 4 + + 24 + 5 + eighth + down + 2 + + + + E + 3 + + 24 + 5 + eighth + down + 2 + end + + + + + G + 1 + 3 + + 24 + 5 + eighth + down + 2 + + + + + D + 4 + + 24 + 5 + eighth + down + 2 + + + + D + 4 + + 24 + 5 + eighth + up + 2 + begin + + + + + + + C + 1 + 4 + + 24 + 5 + eighth + up + 2 + end + + + + + + + G + 2 + + 24 + 5 + eighth + 2 + + + 144 + + + 72 + + + + A + 3 + + 48 + 6 + quarter + down + 2 + + + 24 + + + light-heavy + + + + + + + + + 21.00 + 0.00 + + 150.00 + + + 80.00 + + + + + + + + +

+ + + 1 + + + + + C + + none + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + E + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + C + 1 + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + E + 5 + + 6 + 1 + 32nd + down + 1 + begin + begin + begin + + + + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + 4 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + G + 1 + 4 + + 48 + 1 + quarter + up + 1 + + + + + + + + + B + 4 + + 48 + 1 + quarter + up + 1 + + + + C + + none + + + + A + 4 + + 24 + 1 + eighth + up + 1 + + + + + + + 144 + + + 72 + + + + E + 4 + + 72 + 2 + quarter + + down + 1 + + + 144 + + + + E + 3 + + 24 + 5 + eighth + down + 2 + begin + + + + + C + 1 + 4 + + 24 + 5 + eighth + down + 2 + + + + E + 3 + + 24 + 5 + eighth + down + 2 + continue + + + + + C + 1 + 4 + + 24 + 5 + eighth + down + 2 + + + + E + 3 + + 24 + 5 + eighth + down + 2 + end + + + + + G + 1 + 3 + + 24 + 5 + eighth + down + 2 + + + + + D + 4 + + 24 + 5 + eighth + down + 2 + + + + D + 4 + + 48 + 5 + quarter + up + 2 + + + + + + + C + 1 + 4 + + 24 + 5 + eighth + up + 2 + + + + + + 144 + + + 72 + + + + A + 3 + + 72 + 6 + quarter + + down + 2 + + + light-light + + + + + diff --git a/tests/data/musicxml/test_partial_measures_consecutive.xml b/tests/data/musicxml/test_partial_measures_consecutive.xml new file mode 100644 index 00000000..d825b9db --- /dev/null +++ b/tests/data/musicxml/test_partial_measures_consecutive.xml @@ -0,0 +1,2889 @@ + + + + + K333 + Piano Sonata no. 13 in B flat major + + 3 + Allegretto grazioso + + Wolfgang Amadeus Mozart + + MuseScore 3.3.4 + 2021-04-27 + + + + + + + https://kern.humdrum.org/cgi-bin/ksdata?location=users/craig/classical/mozart/piano/sonata&file=sonata13-3.krn&format=xml + + + + 7.05556 + 40 + + + 1683.78 + 1190.55 + + 56.6929 + 56.6929 + 56.6929 + 113.386 + + + 56.6929 + 56.6929 + 56.6929 + 113.386 + + + + + + + + Piano, Piano right + Pno. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + + + 21.00 + 0.00 + + 71.20 + + + 80.00 + + + + 48 + + -2 + + + 2 + + G + 2 + + + G + 2 + + + + + +

+ + + 1 + + + + + C + + none + + + + F + 5 + + 72 + 1 + quarter + + down + 1 + + + + + + + D + 5 + + 24 + 1 + eighth + down + 1 + + + + + + + C + + none + + + + B + -1 + 4 + + 48 + 1 + quarter + down + 1 + + + + C + + none + + + + B + -1 + 4 + + 48 + 1 + quarter + down + 1 + + + 192 + + + + 48 + 5 + quarter + 2 + + + + +

+ + + 2 + + + + + B + -1 + 3 + + 48 + 5 + quarter + up + 2 + + + + D + 4 + + 48 + 5 + quarter + up + 2 + + + + + + + + + G + 4 + + 48 + 5 + quarter + up + 2 + + + + + + + + + + + + 21.00 + 0.00 + + 142.07 + + + 80.00 + + + + + C + + none + + + + D + -1 + 6 + + 72 + 1 + quarter + + flat + down + 1 + + + + B + -1 + 5 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + begin + + + + B + -1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + + C + + none + + + + D + -1 + 6 + + 72 + 1 + quarter + + down + 1 + + + + B + -1 + 5 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + begin + + + + B + -1 + 5 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + 192 + + + + B + -1 + 2 + + 96 + 5 + half + down + 2 + + + + + B + -1 + 3 + + 96 + 5 + half + down + 2 + + + + G + -1 + 2 + + 96 + 5 + half + flat + up + 2 + + + + + G + -1 + 3 + + 96 + 5 + half + flat + up + 2 + + + + + + C + + none + + + + D + -1 + 6 + + 96 + + 1 + half + flat + down + 1 + + + + + + + + C + + none + + + + D + -1 + 6 + + 12 + + 1 + 16th + down + 1 + begin + begin + + + + + + + B + -1 + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + G + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + E + 5 + + 12 + 1 + 16th + natural + down + 1 + end + end + + + + D + -1 + 5 + + 12 + 1 + 16th + flat + up + 1 + begin + begin + + + + B + -1 + 4 + + 12 + 1 + 16th + up + 1 + continue + continue + + + + G + 4 + + 12 + 1 + 16th + up + 1 + continue + continue + + + + E + 4 + + 12 + 1 + 16th + natural + up + 1 + end + end + + + 192 + + + + E + 2 + + 96 + 5 + half + natural + up + 2 + + + + + + + + E + 3 + + 96 + 5 + half + natural + up + 2 + + + + ad libitum + + 2 + + + + 48 + 5 + quarter + 2 + + + + 48 + 5 + quarter + 2 + + + none + + + + + + 48 + 1 + quarter + 1 + + + + 48 + 1 + quarter + 1 + + + + F + 3 + + 96 + 1 + half + up + 2 + + + + + + + 192 + + + + D + -1 + 4 + + 12 + 5 + 16th + flat + up + 2 + begin + begin + + + + B + -1 + 3 + + 12 + 5 + 16th + up + 2 + continue + continue + + + + G + 3 + + 12 + 5 + 16th + up + 2 + continue + continue + + + + E + 3 + + 12 + 5 + 16th + natural + up + 2 + end + end + + + + D + -1 + 3 + + 12 + 5 + 16th + flat + up + 2 + begin + begin + + + + B + -1 + 2 + + 12 + 5 + 16th + up + 2 + continue + continue + + + + G + 2 + + 12 + 5 + 16th + up + 2 + end + end + + + + 12 + 5 + 16th + 2 + + + + F + 1 + + 96 + 5 + half + down + 2 + + + + + + + + F + 2 + + 96 + 5 + half + down + 2 + + + 192 + + + 84 + + + + E + 2 + + 24 + 6 + eighth + natural + down + 2 + + + 84 + + + none + + + + + + + 21.00 + 0.00 + + 142.07 + + + 85.40 + + + + + F + 3 + + 3 + 1 + 64th + down + 1 + begin + begin + begin + begin + + + + + + + + A + 3 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 3 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + F + 3 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 3 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + A + 3 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + B + -1 + 3 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + C + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + D + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + E + -1 + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + F + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + A + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + B + -1 + 4 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + C + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + D + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + + + + E + -1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + + + + F + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + G + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + A + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + B + -1 + 5 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + C + 6 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + D + 6 + + 3 + 1 + 64th + down + 1 + continue + continue + continue + continue + + + + E + -1 + 6 + + 3 + 1 + 64th + down + 1 + continue + end + end + end + + + + F + 6 + + 24 + 1 + eighth + down + 1 + end + + + + + + + 18 + 1 + 16th + + 1 + + + + + + + B + -1 + 4 + + 6 + 1 + 32nd + down + 1 + + + + + B + -1 + 4 + + 1 + 32nd + up + 1 + begin + begin + begin + + + + + + + + C + 5 + + 1 + 32nd + up + 1 + continue + continue + continue + + + + + D + 5 + + 1 + 32nd + up + 1 + end + end + end + + + + + + + C + 5 + + 192 + 1 + whole + 1 + + + + + + + + + B + 4 + + 6 + 1 + 32nd + natural + down + 1 + begin + begin + begin + + + + + + + C + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + E + -1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + F + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + G + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + A + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + B + -1 + 5 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + C + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + E + -1 + 6 + + 6 + 1 + 32nd + down + 1 + continue + continue + continue + + + + D + 6 + + 6 + 1 + 32nd + down + 1 + end + end + end + + + + + + 384 + + + + 192 + 5 + whole + 2 + + + + 96 + 5 + half + 2 + + + + F + 3 + + 96 + + 5 + half + down + 2 + + + + + + + + A + 3 + + 96 + + 5 + half + down + 2 + + + + + + + + C + 4 + + 96 + + 5 + half + down + 2 + + + + + + + + E + -1 + 4 + + 96 + + 5 + half + down + 2 + + + + + + none + + + + + + + 21.00 + 0.00 + + 142.07 + + + 80.00 + + + + + F + 6 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + E + -1 + 6 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + D + 6 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + + + C + 6 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + + + + E + -1 + 6 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + D + 6 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + C + 6 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + + + B + -1 + 5 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + + + + D + 6 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + C + 6 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + B + -1 + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + + + A + 5 + + 12 + 1 + 16th + down + 1 + end + end + + + + + + + + + C + 6 + + 12 + 1 + 16th + down + 1 + begin + begin + + + + + + + B + -1 + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + A + 5 + + 12 + 1 + 16th + down + 1 + continue + continue + + + + + + + + + G + 5 + + 12 + 1 + 16th + down + 1 + continue + end + + + + + + + + + B + -1 + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + A + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + G + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + + + F + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + + + A + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + G + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + F + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + + + E + -1 + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + + + G + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + F + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + E + -1 + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + + + D + 5 + + 24 + 1 + eighth + down + 1 + end + + + + + + + + + F + 5 + + 48 + 1 + quarter + down + 1 + + + + + + + E + -1 + 5 + + 48 + 1 + quarter + down + 1 + + + + + + + D + 5 + + 48 + 1 + quarter + down + 1 + + + + + + + + + C + 5 + + 48 + 1 + quarter + down + 1 + + + + + + + + + B + 4 + + 24 + 1 + eighth + natural + down + 1 + begin + + + + + + + + + C + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + + + D + -1 + 5 + + 24 + 1 + eighth + flat + down + 1 + continue + + + + + + + + + D + 5 + + 24 + 1 + eighth + natural + down + 1 + continue + + + + + + + + + E + -1 + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + E + 5 + + 24 + 1 + eighth + natural + down + 1 + end + + + + + + 816 + + + + F + 3 + + 48 + + 5 + quarter + down + 2 + + + + + + + + A + 3 + + 48 + + 5 + quarter + down + 2 + + + + + + + + C + 4 + + 48 + + 5 + quarter + down + 2 + + + + + + + + E + -1 + 4 + + 48 + + 5 + quarter + down + 2 + + + + + + + 48 + 5 + quarter + 2 + + + + 96 + 5 + half + 2 + + + + 192 + 5 + whole + 2 + + + + 192 + 5 + whole + 2 + + + + 96 + 5 + half + 2 + + + + 96 + 5 + half + 2 + + + + 48 + 5 + quarter + 2 + + + + + + + 21.00 + 0.00 + + 142.07 + + + 80.00 + + + + + G + 2 + + + + + C + + none + + + + +

+ + + 1 + + + + + in + tempo + + 1 + + + + F + 5 + + 72 + 1 + quarter + + down + 1 + + + + + + + D + 5 + + 24 + 1 + eighth + down + 1 + + + + + + + C + + none + + + + B + -1 + 4 + + 48 + 1 + quarter + down + 1 + + + + C + + none + + + + B + -1 + 4 + + 48 + 1 + quarter + down + 1 + + + 192 + + + + 48 + 5 + quarter + 2 + + + + +

+ + + 2 + + + + + B + -1 + 3 + + 48 + 5 + quarter + up + 2 + + + + D + 4 + + 48 + 5 + quarter + up + 2 + + + + G + 4 + + 48 + 5 + quarter + up + 2 + + + + + + C + + none + + + + E + -1 + 5 + + 48 + 1 + quarter + down + 1 + + + + + + + G + 5 + + 48 + 1 + quarter + down + 1 + + + + + + + C + + none + + + + A + 4 + + 48 + 1 + quarter + up + 1 + + + + C + + none + + + + 24 + 1 + eighth + 1 + + + + A + 4 + + 24 + 1 + eighth + up + 1 + + + 192 + + + + C + 4 + + 48 + 5 + quarter + up + 2 + + + + + + + E + -1 + 4 + + 48 + 5 + quarter + up + 2 + + + + + + + F + 4 + + 48 + 5 + quarter + up + 2 + + + + E + -1 + 4 + + 48 + 5 + quarter + up + 2 + + + + + + C + + none + + + + C + 5 + + 24 + 1 + eighth + down + 1 + begin + + + + + + + B + -1 + 4 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + C + + none + + + + D + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + C + 5 + + 24 + 1 + eighth + down + 1 + end + + + + + + + C + + none + + + + E + -1 + 5 + + 24 + 1 + eighth + down + 1 + begin + + + + + + + D + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + C + + none + + + + F + 5 + + 24 + 1 + eighth + down + 1 + continue + + + + + + + E + -1 + 5 + + 24 + 1 + eighth + down + 1 + end + + + + + + 192 + + + + D + 4 + + 48 + 5 + quarter + up + 2 + + + + F + 4 + + + + + F + 4 + + 96 + 5 + half + up + 2 + + + + C + 4 + + 48 + 5 + quarter + up + 2 + + + 192 + + + 48 + + + + A + 3 + + 48 + 6 + quarter + down + 2 + + + + B + -1 + 3 + + 48 + 6 + quarter + down + 2 + + + + E + -1 + 3 + + 48 + 6 + quarter + down + 2 + + + + diff --git a/tests/test_partial_measures.py b/tests/test_partial_measures.py new file mode 100644 index 00000000..1a14600d --- /dev/null +++ b/tests/test_partial_measures.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This module contains tests for partial measures. +""" +import unittest + +import os +import numpy as np +import partitura +from partitura import load_musicxml +from partitura import score + +import xml.etree.ElementTree as ET + +# TODO tmp, del later +import warnings +warnings.filterwarnings("ignore") + + + # TODO # import error, doesn't recognise path +# from tests import MUSICXML_PARTIAL_MEASURES_TESTFILES +tests_dir = os.path.dirname(os.path.abspath(__file__)) +tests = os.path.dirname(os.path.abspath(__file__)) +partial_measures_test_file = os.path.join(tests, 'data', 'musicxml', 'test_partial_measures.musicxml') +consecutive_partial_measures_test_file = os.path.join(tests, 'data', 'musicxml', 'test_partial_measures_consecutive.musicxml') + +# test_partial_measures.musicxml: Var. V from Sonata K331, 1. mov +spart = load_musicxml(partial_measures_test_file).parts[0] +snote_array = spart.note_array() +# print(type(spart)) # part +# print(type(snote_array)) # ndarray + +# print(dir(spart)) +# print([i for i in dir(spart) if 'measure' in i]) + +# print(type(spart.notes)) # list +# print(dir(spart.notes[0])) # list + +# print(type(spart.measures)) # list +# print(type(spart.measures[0])) # Measure +# print(dir(spart.measures[0])) # + +for measure in spart.measures[:10]: + # print(type(measure.number)) + print(measure.number) + +for note in spart.notes: + print(f"Note {note.id} starts in measure {spart.measure_number_map(note.start.t)}") # breaks + # print(f"Note {note.id} starts is contained in beats {spart.measure_map(note.start.t)}") # works + + + +# print(spart.measure_map) +# print(spart.measure_number_map) +# print(spart.measures) + + +# def create_measures_notes_dict(score_path, piece): + +# measures_notes_dict = {} +# musicxml_file = os.path.join(score_path, 'K%s-%s.musicxml' % (piece[:3], piece[-1])) +# root = ET.parse(musicxml_file) + +# for measure in root.findall("//measure"): +# measure_number = measure.get('number') +# notes = measure.findall('note') +# notes_id = [note.get('id') for note in notes] +# # measures can be organised within parts, or parts within measures. +# # handles the case where the same measures are split between different parts +# if measure_number in measures_notes_dict: +# measures_notes_dict[measure_number].extend(notes_id) +# else: +# measures_notes_dict[measure_number] = notes_id + +# return measures_notes_dict + From 99a655130f7f5cd94a97c6447de471f1c14e4186 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Thu, 1 Jun 2023 12:03:24 +0100 Subject: [PATCH 047/122] minor fix of monotonize times --- partitura/musicanalysis/performance_codec.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index adda0f63..7b85ec67 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -290,7 +290,7 @@ def encode_articulation( pd[grace_mask] = bp # Compute log articulation ratio if (pd / (bp * sd))[0] <= 0: - pass + hook() # warnings is given for this line if we have a negative beat_period - performed notes have reversed timing. articulation[idx] = np.log2(pd / (bp * sd)) @@ -493,6 +493,7 @@ def tempo_by_average( tempo_curve = tempo_fun(input_onsets) + assert((tempo_curve >= 0).all()) if return_onset_idxs: return tempo_curve, input_onsets, unique_onset_idxs else: @@ -637,6 +638,14 @@ def to_matched_score(score: ScoreLike, for a in alignment if (a["label"] == "match" and a["score_id"] in part_by_id) ] + # if isinstance(alignment, np.ndarray): # ATEPP case + # note_pairs = [] + # for aa in alignment: + # if aa["alignID"] != "*" and aa["refID"] != "*": + # p_note = p_na[(np.isclose(p_na['onset_sec'], aa['alignOntime']) & (p_na['pitch'] == int(aa['alignSitch'])))] + # s_note = na[(np.isclose(na['onset_div'], aa['refOntime']) & (na['pitch'] == int(aa['alignSitch'])))] + # if (len(s_note) >= 1) and (len(p_note) >= 1): + # note_pairs.append((s_note[0], p_note[0])) ms = [] # sort according to onset (primary) and pitch (secondary) pitch_onset = [(sn['pitch'].item(), sn['onset_div'].item()) for sn, _ in note_pairs] @@ -910,11 +919,11 @@ def monotonize_times(s, deltas=None): idx = np.arange(_s.shape[0]) # detect the position with value decrease and remove them from the interpolation values - mask = np.diff(_s) >= 0 - mask = np.r_[False, mask] - mask[-1] = False + s_mono = np.maximum.accumulate(s) + mask = np.r_[False, True, (np.diff(s_mono) != 0), False] s_mono = interp1d(idx[mask], _s[mask])(idx[1:-1]) + assert((np.diff(s_mono) >= 0).all()) return s_mono, _deltas[1:-1] From f83c042b44abef6717e6ffddacdfa7bffe24c819 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Fri, 2 Jun 2023 15:52:10 +0100 Subject: [PATCH 048/122] minor fix --- partitura/musicanalysis/performance_codec.py | 22 ++++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 7b85ec67..7bf984fa 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -7,6 +7,7 @@ from typing import Union, Callable import numpy as np import numpy.lib.recfunctions as rfn +import warnings try: import torch @@ -288,10 +289,6 @@ def encode_articulation( # Grace notes have an articulation ratio of 1 sd[grace_mask] = 1 pd[grace_mask] = bp - # Compute log articulation ratio - if (pd / (bp * sd))[0] <= 0: - hook() - # warnings is given for this line if we have a negative beat_period - performed notes have reversed timing. articulation[idx] = np.log2(pd / (bp * sd)) return articulation @@ -638,14 +635,6 @@ def to_matched_score(score: ScoreLike, for a in alignment if (a["label"] == "match" and a["score_id"] in part_by_id) ] - # if isinstance(alignment, np.ndarray): # ATEPP case - # note_pairs = [] - # for aa in alignment: - # if aa["alignID"] != "*" and aa["refID"] != "*": - # p_note = p_na[(np.isclose(p_na['onset_sec'], aa['alignOntime']) & (p_na['pitch'] == int(aa['alignSitch'])))] - # s_note = na[(np.isclose(na['onset_div'], aa['refOntime']) & (na['pitch'] == int(aa['alignSitch'])))] - # if (len(s_note) >= 1) and (len(p_note) >= 1): - # note_pairs.append((s_note[0], p_note[0])) ms = [] # sort according to onset (primary) and pitch (secondary) pitch_onset = [(sn['pitch'].item(), sn['onset_div'].item()) for sn, _ in note_pairs] @@ -900,10 +889,8 @@ def monotonize_times(s, deltas=None): ---------- s : ndarray a sequence of numbers - strict : bool - when True, return a strictly monotonic sequence (default: True) deltas : - + position of the numbers (onset times) Returns ------- ndarray @@ -923,7 +910,10 @@ def monotonize_times(s, deltas=None): mask = np.r_[False, True, (np.diff(s_mono) != 0), False] s_mono = interp1d(idx[mask], _s[mask])(idx[1:-1]) - assert((np.diff(s_mono) >= 0).all()) + try: + assert((np.diff(s_mono) >= 0).all()) + except: + warnings.warn("Monotonize_time produce non-monotonic sequence!") return s_mono, _deltas[1:-1] From 6e527573c214b70106c286b322b0720921a6dacb Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 2 Jun 2023 17:48:22 +0200 Subject: [PATCH 049/122] Added anacrusis and optional support for sanization with measure and ties. --- partitura/musicanalysis/note_array_to_part.py | 65 +++++++++++++------ 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_part.py index b27999b6..f294a2eb 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_part.py @@ -1,4 +1,5 @@ -from partitura.score import Part, PartGroup +import partitura.score +from partitura.score import ScoreLike, Part from partitura.utils import (estimate_symbolic_duration, estimate_clef_properties, key_name_to_fifths_mode, fifths_mode_to_key_name) import warnings import numpy as np @@ -29,10 +30,12 @@ def create_part( key_sigs, part_id=None, part_name=None, + sanitize=True, + anacrusis_divs=0 ): warnings.warn("create_part", stacklevel=2) - part = score.Part(part_id, part_name=part_name) + part = Part(part_id, part_name=part_name, ) part.set_quarter_duration(0, ticks) clef = score.Clef( @@ -89,23 +92,28 @@ def create_part( time_sig = score.TimeSignature(num.item(), den.item()) part.add(time_sig, ts_start, ts_end) - score.add_measures(part) + if anacrusis_divs > 0: + part.add(score.Measure(0), 0, anacrusis_divs) - warnings.warn("tie notes", stacklevel=2) - # tie notes where necessary (across measure boundaries, and within measures - # notes with compound duration) - score.tie_notes(part) + if sanitize: + warnings.warn("Inferring measures", stacklevel=2) + score.add_measures(part) - warnings.warn("find tuplets", stacklevel=2) - # apply simplistic tuplet finding heuristic - score.find_tuplets(part) + warnings.warn("Find and tie notes", stacklevel=2) + # tie notes where necessary (across measure boundaries, and within measures + # notes with compound duration) + score.tie_notes(part) + + warnings.warn("find and ensure tuplets", stacklevel=2) + # apply simplistic tuplet finding heuristic + score.find_tuplets(part) warnings.warn("done create_part", stacklevel=2) return part def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: int = None, assign_note_ids: bool = True, - ensurelist: bool = False, estimate_key: bool = False) -> Union[Union[Part, PartGroup], list]: + ensurelist: bool = False, estimate_key: bool = False, sanitize: bool = True) -> ScoreLike: """ A generic function to transform an enriched note_array to part. @@ -141,7 +149,7 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in parts = [ note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for i, x in enumerate(note_array)] - return parts + return score.Score(partlist=parts) if not isinstance(note_array, np.ndarray): raise TypeError("The note array does not have the correct format.") @@ -150,17 +158,24 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in if "id" not in note_array.dtype.names: note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] - note_array = rfn.merge_arrays((note_array, np.array(note_ids, dtype=[("id", str)])), flatten=True) + note_array = rfn.append_fields(note_array, "id", np.array(note_ids, dtype=' note_array["ts_beats"].max(): @@ -183,6 +198,7 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in note_array[idx]["onset_beat"] = tmp note_array = create_divs_from_beats(note_array) elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch"]]): + anacrusis_mask[note_array["onset_beat"] < 0] = True note_array = create_divs_from_beats(note_array) elif all([x in dtypes for x in ["onset_div", "pitch", "duration_div"]]): pass @@ -211,7 +227,7 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in # Not sure if correct. if part_voice != np.inf: estimated_voices[i] = part_voice - note_array = rfn.merge_arrays((note_array, np.array(estimated_voices, dtype=[("voice", int)])), flatten=True) + note_array = rfn.append_fields(note_array, "voice", np.array(estimated_voices, dtype=int)) if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): warnings.warn("key estimation", stacklevel=2) @@ -223,7 +239,7 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in if n["ts_beats"] != global_key_sigs[-1][1] or n["ts_beat_type"] != global_key_sigs[-1][2]: global_key_sigs.append([n["onset_div"], fifths_mode_to_key_name(n["ks_fifths"], n["ks_mode"])]) global_key_sigs = np.array(global_key_sigs) - # for convenience we add the end times for each time signature + # for convenience, we add the end times for each time signature ks_end_times = np.r_[global_key_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] global_key_sigs = np.column_stack((global_key_sigs, ks_end_times)) @@ -234,14 +250,23 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in if dur != 0: break divs = int((note_array[idx]["duration_div"] / note_array[idx]["duration_beat"])*(note_array[idx]["ts_beat_type"]/4)) + + if np.all(anacrusis_mask == False): + anacrusis_divs = 0 + else: + last_neg_beat = np.max(note_array[anacrusis_mask]["onset_beat"]) + last_neg_divs = np.max(note_array[anacrusis_mask]["onset_div"]) + beat_type = np.max(note_array[anacrusis_mask]["ts_beat_type"]) + difference_from_zero = (0 - last_neg_beat) * divs * (4 / beat_type) + anacrusis_divs = int(last_neg_divs + difference_from_zero) + part = create_part( ticks=divs, note_array=note_array, key_sigs=global_key_sigs, part_id=part_id, part_name=None, + sanitize=sanitize, + anacrusis_divs = anacrusis_divs ) - if ensurelist: - return [part] - else: - return part \ No newline at end of file + return partitura.score.Score(partlist=[part]) From 8af6b8d26782d88cd92b80c5ff513ce1e1a052e4 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 2 Jun 2023 18:10:17 +0200 Subject: [PATCH 050/122] changed name from note_array_to_part to note_array_to_score. --- ...note_array_to_part.py => note_array_to_score.py} | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) rename partitura/musicanalysis/{note_array_to_part.py => note_array_to_score.py} (96%) diff --git a/partitura/musicanalysis/note_array_to_part.py b/partitura/musicanalysis/note_array_to_score.py similarity index 96% rename from partitura/musicanalysis/note_array_to_part.py rename to partitura/musicanalysis/note_array_to_score.py index f294a2eb..4cfa6872 100644 --- a/partitura/musicanalysis/note_array_to_part.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -112,8 +112,8 @@ def create_part( return part -def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: int = None, assign_note_ids: bool = True, - ensurelist: bool = False, estimate_key: bool = False, sanitize: bool = True) -> ScoreLike: +def note_array_to_score(note_array: Union[np.ndarray, list], part_id="", divs: int = None, assign_note_ids: bool = True, + ensurelist: bool = False, estimate_key: bool = False, sanitize: bool = True, scorify=True) -> ScoreLike: """ A generic function to transform an enriched note_array to part. @@ -147,7 +147,7 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in """ if isinstance(note_array, list): parts = [ - note_array_to_part(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist) for + note_array_to_score(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist, scorify=False) for i, x in enumerate(note_array)] return score.Score(partlist=parts) @@ -267,6 +267,9 @@ def note_array_to_part(note_array: Union[np.ndarray, list], part_id="", divs: in part_id=part_id, part_name=None, sanitize=sanitize, - anacrusis_divs = anacrusis_divs + anacrusis_divs=anacrusis_divs ) - return partitura.score.Score(partlist=[part]) + if scorify: + return partitura.score.Score(partlist=[part]) + else: + return part From 58a5cbd0f78d91388d1d7fd40955a4db5980de1f Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 2 Jun 2023 18:10:31 +0200 Subject: [PATCH 051/122] import name corrections. --- partitura/musicanalysis/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/musicanalysis/__init__.py b/partitura/musicanalysis/__init__.py index 1fba3e45..c001ff29 100644 --- a/partitura/musicanalysis/__init__.py +++ b/partitura/musicanalysis/__init__.py @@ -21,7 +21,7 @@ make_rest_features, ) from .performance_codec import encode_performance, decode_performance -from .note_array_to_part import note_array_to_part +from .note_array_to_score import note_array_to_score __all__ = [ "estimate_voices", From edaf3034a0b49592468a0f1b1c6a959ff4818552 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 2 Jun 2023 18:10:38 +0200 Subject: [PATCH 052/122] simple testcase. --- tests/test_note_array.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_note_array.py b/tests/test_note_array.py index 8941e1a2..a4fa59bf 100644 --- a/tests/test_note_array.py +++ b/tests/test_note_array.py @@ -10,6 +10,7 @@ import partitura.score as score from partitura import load_musicxml, load_kern, load_score from partitura.utils.music import note_array_from_part, ensure_notearray +from partitura.musicanalysis import note_array_to_score import numpy as np from tests import NOTE_ARRAY_TESTFILES, KERN_TESTFILES @@ -70,6 +71,14 @@ def test_use_musical_beats2(self): self.assertTrue(score[0]._use_musical_beat == False) self.assertFalse(np.array_equal(score[0].note_array(), note_array)) + def test_note_array_to_score(self): + score = load_musicxml(NOTE_ARRAY_TESTFILES[0]) + note_array = score.note_array(include_time_signature=True) + new_score = note_array_to_score(note_array) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"])) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + def test_notearray_ts_beats(self): part = load_musicxml(NOTE_ARRAY_TESTFILES[0])[0] note_array = note_array_from_part(part, include_time_signature=True) From f98e354f8fe878ebbb5e088076b5ebd7fdb5d3ce Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 2 Jun 2023 18:24:21 +0200 Subject: [PATCH 053/122] Left out tied solution, no test included. --- partitura/io/importmatch.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index 356b2d6a..996edcf3 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -584,16 +584,14 @@ def part_from_matchfile( onset_divs = onset_in_divs[ni] assert onset_divs >= 0 assert np.isclose(onset_divs, onset_in_divs[ni], atol=divs * 0.01) - + is_tied = False articulations = set() if "staccato" in note.ScoreAttributesList or "stac" in note.ScoreAttributesList: articulations.add("staccato") if "accent" in note.ScoreAttributesList: articulations.add("accent") if "leftOutTied" in note.ScoreAttributesList: - # continue introduces inconsistency when save_match - # continue - pass + is_tied = True # dictionary with keyword args with which the Note # (or GraceNote) will be instantiated @@ -679,6 +677,14 @@ def part_from_matchfile( part.add(part_note, onset_divs, offset_divs) + # Check if the note is tied and if so, add the tie information + if is_tied: + for el in part.iter_all(end=offset_divs): + if isinstance(el, score.Note): + if el.step == note_attributes["step"] and el.octave == note_attributes["octave"]: + el.tie_next = part_note + part_note.tie_prev = el + # add time signatures for (ts_beat_time, ts_bar, tsg) in ts: ts_beats = tsg.numerator From 1a528d8fea78d546447fb294957c81fdfc2b1db6 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 2 Jun 2023 18:31:22 +0200 Subject: [PATCH 054/122] added forced fixed size for note features and skipped time_signature_feature for forced size. --- partitura/musicanalysis/note_features.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index c009d60e..e5561aec 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -76,7 +76,8 @@ def make_note_features( part: ScoreLike, feature_functions: Union[List, str], add_idx: bool = False, - include_empty_features: bool = True + include_empty_features: bool = True, + force_fixed_size: bool = False, ) -> Tuple[np.ndarray, List]: """Compute the specified feature functions for a part. @@ -86,7 +87,7 @@ def make_note_features( total number of descriptors of all feature functions that occur in part. - Furthermore the function returns the names of the feature functions. + Furthermore, the function returns the names of the feature functions. A list of strings of size M. The names have the name of the function prepended to the name of the descriptor. For example if a function named `abc_feature` returns descriptors `a`, `b`, and `c`, @@ -108,6 +109,9 @@ def make_note_features( feature. This is useful for debugging. include_empty_features : bool (default: True) If True, features that are empty are included in the output. + Otherwise, they are omitted. + force_fixed_size : bool (default: False) + If True, the output array uses only features that have a fixed size with no new entries added. Returns ------- @@ -142,9 +146,11 @@ def make_note_features( func = getattr(sys.modules[__name__], bf) elif isinstance(bf, types.FunctionType): func = bf + elif force_fixed_size and bf == "time_signature_feature": + continue else: warnings.warn("Ignoring unknown feature function {}".format(bf)) - bf, bn = func(na, part, include_empty_features=include_empty_features) + bf, bn = func(na, part, include_empty_features=(True if force_fixed_size else include_empty_features)) # check if the size and number of the feature function are correct if bf.size != 0: if bf.shape[1] != len(bn): From 91630eea38f869f612d99f0caa995d0be85019ed Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Sun, 4 Jun 2023 11:55:13 +0200 Subject: [PATCH 055/122] updated m21 parser to return partitura score object new general test on the note array --- partitura/io/importmei.py | 2 +- partitura/io/importmusic21.py | 29 ++++++++++++++++++---------- tests/test_m21_import.py | 36 ++++++++++++++++++++++++----------- 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 09e2ff54..6219bde2 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -32,7 +32,7 @@ @deprecated_alias(mei_path="filename") def load_mei(filename: PathLike) -> score.Score: """ - Loads a Mei score from path and returns a list of Partitura.Part + Loads a Mei score from path and returns a partitura Score object. Parameters ---------- diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py index 15984b98..8f13f76f 100644 --- a/partitura/io/importmusic21.py +++ b/partitura/io/importmusic21.py @@ -8,29 +8,35 @@ except ImportError: m21 = None -def load_music21(mei_path: str) -> list: +def load_music21(m21_score : m21.stream.Score) -> pt.score.Score: """ - Loads a Mei score from path and returns a list of Partitura.Part + Loads a music21 score object and returns a Partitura Score object. Parameters ---------- - mei_path : str - The path to an MEI score. + m21_score : :class:`music21.stream.Score` + The music21 score object, produced for example with the `music21.converter.parse()` function. Returns ------- - part_list : list - A list of Partitura Part or GroupPart Objects. + scr: :class:`partitura.score.Score` + A `Score` object """ if m21 is None: raise ImportError("Music21 must be installed for this function to work") - parser = M21Parser(mei_path) - # create parts from the specifications in the mei + parser = M21Parser(m21_score) + # create parts from the specifications in the music21 object parser.create_parts() - # fill parts with the content from the mei + # fill parts with the content from the music21 object parser.fill_parts() - return parser.parts + # create the score + doc_name = m21_score.metadata.title if m21_score.metadata.title is not None else "score" + scr = pt.score.Score( + id=doc_name, + partlist=parser.parts, + ) + return scr class M21Parser: @@ -102,18 +108,21 @@ def fill_part_notes(self, m21_part,pt_part, part_idx): pt_part.add(note, position, position + duration) def fill_part_ts(self, m21_part,pt_part, part_idx): + """ Fills the part with time signatures """ for ts in m21_part.recurse().getElementsByClass(m21.meter.TimeSignature): new_time_signature = pt.score.TimeSignature(ts.numerator, ts.denominator) position = int(ts.getOffsetInHierarchy(self.m21_score)*self.ppq) pt_part.add(new_time_signature, position) def fill_part_ks(self, m21_part,pt_part, part_idx): + """ Fills the part with key signatures """ for ks in m21_part.recurse().getElementsByClass(m21.key.KeySignature): new_key_signature = pt.score.KeySignature(ks.sharps, None) position = int(ks.getOffsetInHierarchy(self.m21_score)*self.ppq) pt_part.add(new_key_signature, position) def fill_part_clefs(self, m21_part,pt_part, part_idx): + """ Fills the part with clefs """ for m21_clef in m21_part.recurse().getElementsByClass(m21.clef.Clef): pt_clef = pt.score.Clef(int(part_idx) +1 , m21_clef.sign, int(m21_clef.line), m21_clef.octaveChange) position = int(m21_clef.getOffsetInHierarchy(self.m21_score)*self.ppq) diff --git a/tests/test_m21_import.py b/tests/test_m21_import.py index 8ecd827f..3fb47813 100644 --- a/tests/test_m21_import.py +++ b/tests/test_m21_import.py @@ -6,33 +6,31 @@ from tests import M21_TESTFILES -from partitura import load_music21, load_mei, EXAMPLE_MEI +from partitura import load_music21 import partitura.score as score -from partitura.io.importmei import MeiParser +import partitura as pt from partitura.utils import compute_pianoroll -from lxml import etree -from xmlschema.names import XML_NAMESPACE import music21 as m21 import numpy as np from pathlib import Path -class TestImportMEI(unittest.TestCase): +class TestImportM21(unittest.TestCase): def test_grace_note(self): m21_score = m21.converter.parse(M21_TESTFILES[1]) - part_list = load_music21(m21_score) - part = list(score.iter_parts(part_list))[0] + pt_score = load_music21(m21_score) + part = pt_score.parts[0] grace_notes = list(part.iter_all(score.GraceNote)) self.assertTrue(len(part.note_array()) == 7) self.assertTrue(len(grace_notes) == 4) def test_clef(self): m21_score = m21.converter.parse(M21_TESTFILES[0]) - part_list = load_music21(m21_score) + pt_score = load_music21(m21_score) # test on part 2 - part2 = list(score.iter_parts(part_list))[2] + part2 = pt_score.parts[2] clefs2 = list(part2.iter_all(score.Clef)) self.assertTrue(len(clefs2) == 2) self.assertTrue(clefs2[0].start.t == 0) @@ -46,7 +44,7 @@ def test_clef(self): self.assertTrue(clefs2[1].staff == 3) self.assertTrue(clefs2[1].octave_change == 0) # test on part 3 - part3 = list(score.iter_parts(part_list))[3] + part3 = pt_score.parts[3] clefs3 = list(part3.iter_all(score.Clef)) self.assertTrue(len(clefs3) == 2) self.assertTrue(clefs3[0].start.t == 0) @@ -54,4 +52,20 @@ def test_clef(self): self.assertTrue(clefs3[1].sign == "G") self.assertTrue(clefs3[1].line == 2) self.assertTrue(clefs3[1].staff == 4) - self.assertTrue(clefs3[1].octave_change == -1) \ No newline at end of file + self.assertTrue(clefs3[1].octave_change == -1) + + # check if the note array computed directly from partitura is the same as the one computed by importing first with m21 + def test_note_array1(self): + # load score from music21 + m21_score = m21.converter.parse(M21_TESTFILES[0]) + pt_score_from_m21 = load_music21(m21_score) + # load score directly from partitura + pt_score_direct = pt.load_score(M21_TESTFILES[0]) + + # compare the note arrays + note_array_from_m21 = pt_score_from_m21.note_array() + note_array_direct = pt_score_direct.note_array() + self.assertTrue(np.array_equal(note_array_from_m21["pitch"], note_array_direct["pitch"])) + self.assertTrue(np.array_equal(note_array_from_m21["onset_beat"], note_array_direct["onset_beat"])) + self.assertTrue(np.array_equal(note_array_from_m21["duration_beat"], note_array_direct["duration_beat"])) + From 65b8e1156d982832fc64254c16aa266144014a47 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Mon, 5 Jun 2023 09:12:10 +0200 Subject: [PATCH 056/122] Extra test and black formatting --- partitura/io/importmusic21.py | 129 ++++++++++++++++++++-------------- tests/__init__.py | 9 ++- tests/test_m21_import.py | 57 ++++++++++++--- 3 files changed, 133 insertions(+), 62 deletions(-) diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py index 8f13f76f..47e27074 100644 --- a/partitura/io/importmusic21.py +++ b/partitura/io/importmusic21.py @@ -3,12 +3,14 @@ from fractions import Fraction from partitura.utils import generic + try: import music21 as m21 except ImportError: m21 = None -def load_music21(m21_score : m21.stream.Score) -> pt.score.Score: + +def load_music21(m21_score: m21.stream.Score) -> pt.score.Score: """ Loads a music21 score object and returns a Partitura Score object. @@ -31,7 +33,9 @@ def load_music21(m21_score : m21.stream.Score) -> pt.score.Score: # fill parts with the content from the music21 object parser.fill_parts() # create the score - doc_name = m21_score.metadata.title if m21_score.metadata.title is not None else "score" + doc_name = ( + m21_score.metadata.title if m21_score.metadata.title is not None else "score" + ) scr = pt.score.Score( id=doc_name, partlist=parser.parts, @@ -40,6 +44,9 @@ def load_music21(m21_score : m21.stream.Score) -> pt.score.Score: class M21Parser: + """ + Class to parse a music21 score object and create a partitura score object from it. + """ def __init__(self, m21_score): self.m21_score = m21_score @@ -47,23 +54,28 @@ def __init__(self, m21_score): def create_parts(self): # create the part list - self.parts = [pt.score.Part(m21_part.id, m21_part.partName, quarter_duration=self.ppq) for m21_part in self.m21_score.parts] - + self.parts = [ + pt.score.Part(m21_part.id, m21_part.partName, quarter_duration=self.ppq) + for m21_part in self.m21_score.parts + ] + def fill_parts(self): # fill parts with the content of the score - for part_idx, (m21_part, pt_part) in enumerate(zip(self.m21_score.parts, self.parts)): + for part_idx, (m21_part, pt_part) in enumerate( + zip(self.m21_score.parts, self.parts) + ): # fill notes - self.fill_part_notes(m21_part,pt_part, part_idx) + self.fill_part_notes(m21_part, pt_part, part_idx) # fill rests - self.fill_part_rests(m21_part,pt_part, part_idx) + self.fill_part_rests(m21_part, pt_part, part_idx) # fill key signatures - self.fill_part_ks(m21_part,pt_part, part_idx) + self.fill_part_ks(m21_part, pt_part, part_idx) # fill time signatures - self.fill_part_ts(m21_part,pt_part, part_idx) + self.fill_part_ts(m21_part, pt_part, part_idx) # fill with clefs - self.fill_part_clefs(m21_part,pt_part, part_idx) + self.fill_part_clefs(m21_part, pt_part, part_idx) - def fill_part_rests(self, m21_part,pt_part, part_idx): + def fill_part_rests(self, m21_part, pt_part, part_idx): for m21_rest in m21_part.recurse().getElementsByClass(m21.note.Rest): pt_rest = pt.score.Rest( id=m21_rest.id, @@ -73,82 +85,95 @@ def fill_part_rests(self, m21_part,pt_part, part_idx): articulations=None, ) # add rest to the part - position = int(m21_rest.getOffsetBySite(self.m21_score.recurse())*self.ppq) - duration = int(m21_rest.duration.quarterLength*self.ppq) + position = int( + m21_rest.getOffsetBySite(self.m21_score.recurse()) * self.ppq + ) + duration = int(m21_rest.duration.quarterLength * self.ppq) pt_part.add(pt_rest, position, position + duration) - def fill_part_notes(self, m21_part,pt_part, part_idx): + def fill_part_notes(self, m21_part, pt_part, part_idx): for generic_note in m21_part.recurse().notes: for i_pitch, pitch in enumerate(generic_note.pitches): if generic_note.duration.isGrace: note = pt.score.GraceNote( - grace_type="acciaccatura" if generic_note.duration.slash else "appoggiatura", + grace_type="acciaccatura" + if generic_note.duration.slash + else "appoggiatura", step=pitch.step, octave=pitch.octave, - alter=pitch.accidental.alter if pitch.accidental is not None else None, - id= "{}_{}".format(generic_note.id,i_pitch), + alter=pitch.accidental.alter + if pitch.accidental is not None + else None, + id="{}_{}".format(generic_note.id, i_pitch), voice=self.find_voice(generic_note), staff=part_idx + 1, symbolic_duration=generic_note.duration.type, articulations=None, # TODO : add articulation ) else: - note= pt.score.Note( - step= pitch.step, - octave= pitch.octave, - alter=pitch.accidental.alter if pitch.accidental is not None else None, - id="{}_{}".format(generic_note.id,i_pitch), - voice= self.find_voice(generic_note), - staff= part_idx + 1, + note = pt.score.Note( + step=pitch.step, + octave=pitch.octave, + alter=pitch.accidental.alter + if pitch.accidental is not None + else None, + id="{}_{}".format(generic_note.id, i_pitch), + voice=self.find_voice(generic_note), + staff=part_idx + 1, symbolic_duration=generic_note.duration.type, articulations=None, # TODO : add articulation ) - position = int(generic_note.getOffsetInHierarchy(self.m21_score)*self.ppq) - duration = int(generic_note.duration.quarterLength*self.ppq) + position = int( + generic_note.getOffsetInHierarchy(self.m21_score) * self.ppq + ) + duration = int(generic_note.duration.quarterLength * self.ppq) pt_part.add(note, position, position + duration) - def fill_part_ts(self, m21_part,pt_part, part_idx): - """ Fills the part with time signatures """ + def fill_part_ts(self, m21_part, pt_part, part_idx): + """Fills the part with time signatures""" for ts in m21_part.recurse().getElementsByClass(m21.meter.TimeSignature): new_time_signature = pt.score.TimeSignature(ts.numerator, ts.denominator) - position = int(ts.getOffsetInHierarchy(self.m21_score)*self.ppq) + position = int(ts.getOffsetInHierarchy(self.m21_score) * self.ppq) pt_part.add(new_time_signature, position) - def fill_part_ks(self, m21_part,pt_part, part_idx): - """ Fills the part with key signatures """ + def fill_part_ks(self, m21_part, pt_part, part_idx): + """Fills the part with key signatures""" for ks in m21_part.recurse().getElementsByClass(m21.key.KeySignature): new_key_signature = pt.score.KeySignature(ks.sharps, None) - position = int(ks.getOffsetInHierarchy(self.m21_score)*self.ppq) + position = int(ks.getOffsetInHierarchy(self.m21_score) * self.ppq) pt_part.add(new_key_signature, position) - def fill_part_clefs(self, m21_part,pt_part, part_idx): - """ Fills the part with clefs """ + def fill_part_clefs(self, m21_part, pt_part, part_idx): + """Fills the part with clefs""" for m21_clef in m21_part.recurse().getElementsByClass(m21.clef.Clef): - pt_clef = pt.score.Clef(int(part_idx) +1 , m21_clef.sign, int(m21_clef.line), m21_clef.octaveChange) - position = int(m21_clef.getOffsetInHierarchy(self.m21_score)*self.ppq) + pt_clef = pt.score.Clef( + int(part_idx) + 1, + m21_clef.sign, + int(m21_clef.line), + m21_clef.octaveChange, + ) + position = int(m21_clef.getOffsetInHierarchy(self.m21_score) * self.ppq) pt_part.add(pt_clef, position) - def find_voice(self, m21_general_note): """Return the voice for an music21 general note""" - return 1 if type(m21_general_note.activeSite) is m21.stream.Measure else m21_general_note.activeSite.id - + return ( + 1 + if type(m21_general_note.activeSite) is m21.stream.Measure + else m21_general_note.activeSite.id + ) def find_ppq(self): - """Finds the ppq """ - + """Finds the ppq""" + def fractional_gcd(inputs): - denoms = np.array([Fraction(e).denominator for e in inputs],dtype = int) + denoms = np.array([Fraction(e).denominator for e in inputs], dtype=int) denoms_lcm = np.lcm.reduce(denoms) - return Fraction(1,denoms_lcm) + return Fraction(1, denoms_lcm) - durs = [el.duration.quarterLength for el in self.m21_score.recurse().getElementsByClass('Music21Object') if el.duration.quarterLength!=0 ] + durs = [ + el.duration.quarterLength + for el in self.m21_score.recurse().getElementsByClass("Music21Object") + if el.duration.quarterLength != 0 + ] return fractional_gcd(durs).denominator - - - - - - - - diff --git a/tests/__init__.py b/tests/__init__.py index 3947cc58..d49e62eb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -178,7 +178,14 @@ ] KERN_TIES = [os.path.join(KERN_PATH, fn) for fn in ["tie_mismatch.krn"]] -M21_TESTFILES = [os.path.join(DATA_PATH, "musicxml","test_clefs_tss.xml"), os.path.join(DATA_PATH, "musicxml","test_grace_note.xml")] +M21_TESTFILES = [ + os.path.join(DATA_PATH, "musicxml", fn) + for fn in [ + "test_clefs_tss.xml", + "test_grace_note.xml", + "test_chew_vosa_example.xml", + ] +] HARMONY_TESTFILES = [os.path.join(MUSICXML_PATH, fn) for fn in ["test_harmony.musicxml"]] MOZART_VARIATION_FILES = dict( diff --git a/tests/test_m21_import.py b/tests/test_m21_import.py index 3fb47813..889bd5e9 100644 --- a/tests/test_m21_import.py +++ b/tests/test_m21_import.py @@ -1,23 +1,18 @@ """ This file contains test functions for music21 import """ - import unittest - from tests import M21_TESTFILES from partitura import load_music21 import partitura.score as score import partitura as pt -from partitura.utils import compute_pianoroll -import music21 as m21 +import music21 as m21 import numpy as np -from pathlib import Path class TestImportM21(unittest.TestCase): - def test_grace_note(self): m21_score = m21.converter.parse(M21_TESTFILES[1]) pt_score = load_music21(m21_score) @@ -65,7 +60,51 @@ def test_note_array1(self): # compare the note arrays note_array_from_m21 = pt_score_from_m21.note_array() note_array_direct = pt_score_direct.note_array() - self.assertTrue(np.array_equal(note_array_from_m21["pitch"], note_array_direct["pitch"])) - self.assertTrue(np.array_equal(note_array_from_m21["onset_beat"], note_array_direct["onset_beat"])) - self.assertTrue(np.array_equal(note_array_from_m21["duration_beat"], note_array_direct["duration_beat"])) + self.assertTrue( + np.array_equal(note_array_from_m21["pitch"], note_array_direct["pitch"]) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["onset_beat"], note_array_direct["onset_beat"] + ) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["duration_beat"], note_array_direct["duration_beat"] + ) + ) + def test_note_array2(self): + # load score from music21 + m21_score = m21.converter.parse(M21_TESTFILES[2]) + pt_score_from_m21 = load_music21(m21_score) + # load score directly from partitura + pt_score_direct = pt.load_score(M21_TESTFILES[2]) + + # compare the note arrays + note_array_from_m21 = pt_score_from_m21.note_array() + note_array_direct = pt_score_direct.note_array() + self.assertTrue( + np.array_equal(note_array_from_m21["pitch"], note_array_direct["pitch"]) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["onset_beat"], note_array_direct["onset_beat"] + ) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["duration_beat"], note_array_direct["duration_beat"] + ) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["onset_quarter"], note_array_direct["onset_quarter"] + ) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["duration_quarter"], + note_array_direct["duration_quarter"], + ) + ) From 6731eebec38f9951555a4bbaf1bb02422e1621e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 5 Jun 2023 10:05:58 +0200 Subject: [PATCH 057/122] add tests for clip_note_off and reindex_notes --- partitura/utils/music.py | 12 +++---- tests/test_utils.py | 75 +++++++++++++++++++++++++++++----------- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 98433a4c..24d3ba62 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3343,9 +3343,9 @@ def slice_ppart_by_time( Returns ------- - ppart_slice : `PerformedPart` object - A copy of input ppart containing notes, programs and control information - only between `start_time` and `end_time` of ppart + ppart_slice : `PerformedPart` object + A copy of input ppart containing notes, programs and control + information only between `start_time` and `end_time` of ppart """ from partitura.performance import PerformedPart @@ -3360,7 +3360,7 @@ def slice_ppart_by_time( # -> check `adjust_offsets_w_sustain` in partitura.performance ppart_slice = PerformedPart([{'note_on': 0, 'note_off': 0}]) - # get ppq if PerformedPart contains it, + # get ppq if PerformedPart contains it, # else skip time_tick info when e.g. created with 'load_performance_midi' try: ppq = ppart.ppq @@ -3374,7 +3374,7 @@ def slice_ppart_by_time( if cc['time'] >= start_time and cc['time'] <= end_time: new_cc = cc.copy() new_cc['time'] -= start_time - if ppq: + if ppq: new_cc['time_tick'] = int(2 * ppq * cc['time']) controls_slice.append(new_cc) ppart_slice.controls = controls_slice @@ -3405,7 +3405,7 @@ def slice_ppart_by_time( new_note['note_on_tick'] = 0 new_note['note_off_tick'] = int(2 * ppq * new_note['note_off']) if reindex_notes: - new_note['id'] = 'n' + str(note_id) + new_note['id'] = f"n{note_id}" note_id += 1 notes_slice.append(new_note) # todo - combine both cases diff --git a/tests/test_utils.py b/tests/test_utils.py index 8574a11c..e2708301 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -435,30 +435,65 @@ def test_sliceperf(self): target_note_array = note_array[idx] - ppart_slice = music.slice_ppart_by_time( - ppart=ppart, - start_time=start_time, - end_time=end_time, - clip_note_off=False, - reindex_notes=False, - ) + def test_arrays(clip_note_off, reindex_notes): + + # Test without clipping note offs + ppart_slice = music.slice_ppart_by_time( + ppart=ppart, + start_time=start_time, + end_time=end_time, + clip_note_off=clip_note_off, + reindex_notes=reindex_notes, + ) + slice_note_array = ppart_slice.note_array() - slice_note_array = ppart_slice.note_array() + self.assertTrue(len(target_note_array) == len(slice_note_array)) + self.assertTrue( + slice_note_array["onset_sec"].max() <= (end_time - start_time) + ) - self.assertTrue(len(target_note_array) == len(slice_note_array)) - self.assertTrue(slice_note_array["onset_sec"].max() <= (end_time - start_time)) - self.assertTrue( - np.isclose( - target_note_array["onset_sec"].min() - start_time, - slice_note_array["onset_sec"].min(), + if clip_note_off: + self.assertTrue( + ( + slice_note_array["onset_sec"] + slice_note_array["duration_sec"] + ).max() + <= (end_time - start_time) + ) + else: + self.assertTrue( + ( + slice_note_array["onset_sec"] + slice_note_array["duration_sec"] + ).max() + >= (end_time - start_time) + ) + + self.assertTrue( + np.isclose( + target_note_array["onset_sec"].min() - start_time, + slice_note_array["onset_sec"].min(), + ) ) - ) - self.assertTrue( - np.isclose( - target_note_array["onset_sec"].max() - start_time, - slice_note_array["onset_sec"].max(), + self.assertTrue( + np.isclose( + target_note_array["onset_sec"].max() - start_time, + slice_note_array["onset_sec"].max(), + ) ) - ) + + nidx = slice_note_array["id"] + nidx.sort() + + if reindex_notes: + tidx = np.array([f"n{idx}" for idx in range(len(nidx))]) + else: + tidx = target_note_array["id"] + + tidx.sort() + self.assertTrue(np.all(nidx == tidx)) + + for cno in (True, False): + for rin in (True, False): + test_arrays(cno, rin) class TestGenericUtils(unittest.TestCase): From cbacfc4598e066d612abaaa76043dd51f4322ffe Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Mon, 5 Jun 2023 10:11:54 +0200 Subject: [PATCH 058/122] added ties and tests with ties --- partitura/io/importmei.py | 2 +- partitura/io/importmusic21.py | 33 +++++++++++++++++++++++++++++++-- tests/__init__.py | 1 + tests/test_m21_import.py | 24 ++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 6219bde2..dcc8285d 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -1037,7 +1037,7 @@ def _tie_notes(self, section_el, part_list): # remove the # in first position start_id = start_id[1:] end_id = end_id[1:] - # set tie prev and tie next in partira note objects + # set tie prev and tie next in partitura note objects all_notes_dict[start_id].tie_next = all_notes_dict[end_id] all_notes_dict[end_id].tie_prev = all_notes_dict[start_id] diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py index 47e27074..1176bc9e 100644 --- a/partitura/io/importmusic21.py +++ b/partitura/io/importmusic21.py @@ -74,6 +74,8 @@ def fill_parts(self): self.fill_part_ts(m21_part, pt_part, part_idx) # fill with clefs self.fill_part_clefs(m21_part, pt_part, part_idx) + # handle ties + self.tie_notes(self.m21_score, self.parts) def fill_part_rests(self, m21_part, pt_part, part_idx): for m21_rest in m21_part.recurse().getElementsByClass(m21.note.Rest): @@ -104,7 +106,8 @@ def fill_part_notes(self, m21_part, pt_part, part_idx): alter=pitch.accidental.alter if pitch.accidental is not None else None, - id="{}_{}".format(generic_note.id, i_pitch), + # id="{}_{}".format(generic_note.id, i_pitch), + id = generic_note.id, voice=self.find_voice(generic_note), staff=part_idx + 1, symbolic_duration=generic_note.duration.type, @@ -117,7 +120,8 @@ def fill_part_notes(self, m21_part, pt_part, part_idx): alter=pitch.accidental.alter if pitch.accidental is not None else None, - id="{}_{}".format(generic_note.id, i_pitch), + # id="{}_{}".format(generic_note.id, i_pitch), + id = generic_note.id, voice=self.find_voice(generic_note), staff=part_idx + 1, symbolic_duration=generic_note.duration.type, @@ -142,6 +146,31 @@ def fill_part_ks(self, m21_part, pt_part, part_idx): new_key_signature = pt.score.KeySignature(ks.sharps, None) position = int(ks.getOffsetInHierarchy(self.m21_score) * self.ppq) pt_part.add(new_key_signature, position) + + def tie_notes(self, m21_score, pt_part_list): + """Fills the part with ties""" + # create a dict of id : note, to speed up search + all_notes = [ + note + for part in pt_part_list + for note in part.iter_all(cls=pt.score.Note) + ] + all_notes_dict = {note.id: note for note in all_notes} + for m21_note in m21_score.recurse().getElementsByClass(m21.note.Note): + if m21_note.tie is not None: + if m21_note.tie.type == "start": + start_id = m21_note.id + end_id = m21_note.next('Note').id + elif m21_note.tie.type == "stop": + pass # music21 don't require the stop to be set + elif m21_note.tie.type == "continue": + start_id = m21_note.id + end_id = m21_note.next('Note').id + + # set tie prev and tie next in partitura note objects + all_notes_dict[start_id].tie_next = all_notes_dict[end_id] + all_notes_dict[end_id].tie_prev = all_notes_dict[start_id] + def fill_part_clefs(self, m21_part, pt_part, part_idx): """Fills the part with clefs""" diff --git a/tests/__init__.py b/tests/__init__.py index d49e62eb..2092df1c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -184,6 +184,7 @@ "test_clefs_tss.xml", "test_grace_note.xml", "test_chew_vosa_example.xml", + "test_note_ties.xml", ] ] HARMONY_TESTFILES = [os.path.join(MUSICXML_PATH, fn) for fn in ["test_harmony.musicxml"]] diff --git a/tests/test_m21_import.py b/tests/test_m21_import.py index 889bd5e9..472f8e0e 100644 --- a/tests/test_m21_import.py +++ b/tests/test_m21_import.py @@ -108,3 +108,27 @@ def test_note_array2(self): note_array_direct["duration_quarter"], ) ) + + def test_note_array3(self): + # load score from music21 + m21_score = m21.converter.parse(M21_TESTFILES[3]) + pt_score_from_m21 = load_music21(m21_score) + # load score directly from partitura + pt_score_direct = pt.load_score(M21_TESTFILES[3]) + + # compare the note arrays + note_array_from_m21 = pt_score_from_m21.note_array() + note_array_direct = pt_score_direct.note_array() + self.assertTrue( + np.array_equal(note_array_from_m21["pitch"], note_array_direct["pitch"]) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["onset_beat"], note_array_direct["onset_beat"] + ) + ) + self.assertTrue( + np.array_equal( + note_array_from_m21["duration_beat"], note_array_direct["duration_beat"] + ) + ) From 7be5493c24c2e119ccdd8957e725579addeec1aa Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Mon, 5 Jun 2023 10:27:20 +0200 Subject: [PATCH 059/122] Update partitura_unittests.yml Added optional dependencies install step (for now only music21) --- .github/workflows/partitura_unittests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index 1b75dcce..390523d3 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -25,6 +25,9 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . + - name: Instal Optional dependencies + run: | + pip install music21 - name: Run Tests run: | pip install coverage From 71a5b76e7ee69f370a1717d35fc6e570faecdb74 Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Mon, 5 Jun 2023 11:07:50 +0200 Subject: [PATCH 060/122] Update partitura_unittests.yml Added other optional imports such as Pillow, musescore and torch --- .github/workflows/partitura_unittests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index 390523d3..949a7160 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -27,7 +27,8 @@ jobs: pip install . - name: Instal Optional dependencies run: | - pip install music21 + pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 + pip install torch==1.5.0+cpu - name: Run Tests run: | pip install coverage From 1365434b8566a1269dc3c9dccbde667e482bd5f7 Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Mon, 5 Jun 2023 11:12:07 +0200 Subject: [PATCH 061/122] Update partitura_unittests.yml Version correction for Torch, it might still fail because of OS compatibility issues --- .github/workflows/partitura_unittests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index 949a7160..d2b4388a 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -28,7 +28,7 @@ jobs: - name: Instal Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 - pip install torch==1.5.0+cpu + pip install torch - name: Run Tests run: | pip install coverage From bfeb22552d7708bea0a9aec441c0818e6b97eb32 Mon Sep 17 00:00:00 2001 From: sildater Date: Mon, 5 Jun 2023 12:03:43 +0200 Subject: [PATCH 062/122] remove torch dependency --- partitura/musicanalysis/performance_codec.py | 44 ++++++-------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 58efbb7a..b16dd448 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -8,18 +8,6 @@ import numpy as np import numpy.lib.recfunctions as rfn -try: - import torch -except ImportError: - # Dummy module to avoid ImportErrors - class DummyTorch(object): - Tensor = np.ndarray - - def __init__(self): - pass - - torch = DummyTorch() - from partitura.score import Part, ScoreLike from partitura.performance import PerformedPart, PerformanceLike @@ -760,17 +748,13 @@ def monotonize_times(s, deltas=None): def notewise_to_onsetwise(notewise_inputs, unique_onset_idxs): """Agregate basis functions per onset""" - if isinstance(notewise_inputs, np.ndarray): - if notewise_inputs.ndim == 1: - shape = len(unique_onset_idxs) - else: - shape = (len(unique_onset_idxs),) + notewise_inputs.shape[1:] - onsetwise_inputs = np.zeros(shape, dtype=notewise_inputs.dtype) - elif isinstance(notewise_inputs, torch.Tensor): - onsetwise_inputs = torch.zeros( - (len(unique_onset_idxs), notewise_inputs.shape[1]), - dtype=notewise_inputs.dtype, - ) + + if notewise_inputs.ndim == 1: + shape = len(unique_onset_idxs) + else: + shape = (len(unique_onset_idxs),) + notewise_inputs.shape[1:] + onsetwise_inputs = np.zeros(shape, dtype=notewise_inputs.dtype) + for i, uix in enumerate(unique_onset_idxs): try: @@ -784,14 +768,12 @@ def notewise_to_onsetwise(notewise_inputs, unique_onset_idxs): def onsetwise_to_notewise(onsetwise_input, unique_onset_idxs): """Expand onsetwise predictions for each note""" n_notes = sum([len(uix) for uix in unique_onset_idxs]) - if isinstance(onsetwise_input, np.ndarray): - if onsetwise_input.ndim == 1: - shape = n_notes - else: - shape = (n_notes,) + onsetwise_input.shape[1:] - notewise_inputs = np.zeros(shape, dtype=onsetwise_input.dtype) - elif isinstance(onsetwise_input, torch.Tensor): - notewise_inputs = torch.zeros(n_notes, dtype=onsetwise_input.dtype) + if onsetwise_input.ndim == 1: + shape = n_notes + else: + shape = (n_notes,) + onsetwise_input.shape[1:] + notewise_inputs = np.zeros(shape, dtype=onsetwise_input.dtype) + for i, uix in enumerate(unique_onset_idxs): notewise_inputs[uix] = onsetwise_input[[i]] return notewise_inputs From e9a2938ce80fc7c835e105534cbbb4aac22a15cc Mon Sep 17 00:00:00 2001 From: Patricia Hu Date: Mon, 5 Jun 2023 12:16:29 +0200 Subject: [PATCH 063/122] partial/irregular measures fix --- partitura/__init__.py | 1 + partitura/io/importmusicxml.py | 22 +- partitura/score.py | 18 +- .../musicxml/test_partial_measures.musicxml | 11740 ---------------- tests/data/musicxml/test_partial_measures.xml | 148 + .../test_partial_measures_consecutive.xml | 2929 +--- tests/test_partial_measures.py | 118 +- 7 files changed, 310 insertions(+), 14666 deletions(-) delete mode 100644 tests/data/musicxml/test_partial_measures.musicxml create mode 100644 tests/data/musicxml/test_partial_measures.xml diff --git a/partitura/__init__.py b/partitura/__init__.py index 074b8726..cb494987 100644 --- a/partitura/__init__.py +++ b/partitura/__init__.py @@ -26,6 +26,7 @@ from . import musicanalysis from .musicanalysis import make_note_features, compute_note_array, full_note_array + # define a version variable __version__ = pkg_resources.get_distribution("partitura").version diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index 2e485767..74d49c55 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -299,12 +299,12 @@ def load_musicxml( lyricist=lyricist, copyright=copyright, ) - + return scr def _parse_parts(document, part_dict): - print('_parse_parts') # NOTE del + # print('_parse_parts') # NOTE del """ Populate the Part instances that are the values of `part_dict` with the musical content in document. @@ -330,9 +330,9 @@ def _parse_parts(document, part_dict): _handle_new_page(position, part, ongoing) _handle_new_system(position, part, ongoing) - for measure_el in part_el.xpath("measure"): + for mc, measure_el in enumerate(part_el.xpath("measure")): position, doc_order = _handle_measure( - measure_el, position, part, ongoing, doc_order + measure_el, position, part, ongoing, doc_order, mc+1 ) # complete unfinished endings @@ -457,14 +457,14 @@ def _parse_parts(document, part_dict): # shift.applied = True -def _handle_measure(measure_el, position, part, ongoing, doc_order): +def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_counter): # print('_handle_measure') # NOTE del """ Parse a ... element, adding it and its contents to the part. """ # make a measure object - measure = make_measure(measure_el) + measure = make_measure(measure_el, measure_counter) # add the start of the measure to the time line part.add(measure, position) @@ -698,15 +698,17 @@ def _handle_new_system(position, part, ongoing): ongoing["system"] = system -def make_measure(xml_measure): - # print('make_measure fn') # NOTE del later +def make_measure(xml_measure, measure_counter): measure = score.Measure() # try: # measure.number = int(xml_measure.attrib['number']) # except: # LOGGER.warn('No number attribute found for measure') - measure.number = get_value_from_attribute(xml_measure, "number", int) # TODO this isn't called - # measure.number = get_value_from_attribute(xml_measure, "number", str) # TODO + + measure.number = measure_counter + measure.name = get_value_from_attribute(xml_measure, "number", str) + + # print(f'{measure}') return measure diff --git a/partitura/score.py b/partitura/score.py index 22711a3e..494c10dc 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -270,7 +270,6 @@ def measure_map(self): @property def measure_number_map(self): - print("measure_number_map") # NOTE del """A function mapping timeline times to the measure number of the measure they are contained in. The function can take scalar values or lists/arrays of values. @@ -283,8 +282,6 @@ def measure_number_map(self): """ # operations to avoid None values and filter them efficiently. m_it = self.measures - print(type(m_it)) - print([m.number for m in self.measures]) measures = np.array( [ @@ -2404,22 +2401,27 @@ class Measure(TimedObject): Parameters ---------- - number : int or None, optional - The number of the measure. Defaults to None + counter : int + The running count independent of measure regularity/ volta endings, continuously counting up all measures in a musicxml score file (ignoring unfoldings) and always starting from one. + number : string, optional + The number of the measure in a given musicxml score file. Can be a non-number in case of volta endings, irregular measures (i.e., pickup measures in the middle of the piece). Defaults to None Attributes ---------- - number : intp + number : str + See parameters + counter : int See parameters """ - def __init__(self, number=None): + def __init__(self, number=None, name=None): super().__init__() self.number = number + self.name = name def __str__(self): - return f"{super().__str__()} number={self.number}" + return f"{super().__str__():16s} number={self.number} name={self.name}" @property def page(self): diff --git a/tests/data/musicxml/test_partial_measures.musicxml b/tests/data/musicxml/test_partial_measures.musicxml deleted file mode 100644 index fbd93da5..00000000 --- a/tests/data/musicxml/test_partial_measures.musicxml +++ /dev/null @@ -1,11740 +0,0 @@ - - - - - K331 - Piano Sonata no. 11 in A major - - 1 - Andante grazioso - - Wolfgang Amadeus Mozart - - MuseScore 3.3.4 - 2021-04-27 - - - - - - - http://musescore.com/user/14709081/scores/4745241 - - - - 7.05556 - 40 - - - 1584 - 1224 - - 56.6929 - 56.6929 - 56.6929 - 113.386 - - - 56.6929 - 56.6929 - 56.6929 - 113.386 - - - - - - - Sonata No. XI in A Major - - - Wolfgang Amadeus Mozart (K. 331) - - - - Piano - Pno. - - Piano - - - - 1 - 1 - 78.7402 - 0 - - - - - - - - - 21.00 - -0.00 - - 141.15 - - - 80.00 - - - - heavy-light - - - - - - - - Var. V. - - 1 - - - - Adagio - - - - eighth - 60 - - - 1 - - - - - -

- - - 1 - - - - - C - - none - - - - C - 1 - 5 - - 24 - 1 - eighth - down - 1 - begin - - - - C - 1 - 5 - - 36 - 1 - eighth - - down - 1 - continue - - - - - - - C - - none - - - - D - 5 - - 12 - 1 - 16th - down - 1 - end - backward hook - - - - - - - C - - none - - - - F - 1 - 5 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - E - 5 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - - 12 - 1 - 16th - 1 - - - - E - 5 - - 12 - 1 - 16th - down - 1 - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - 144 - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - B - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - - - - 21.00 - -0.00 - - 150.00 - - - 82.59 - - - - - C - - none - - - - C - 1 - 5 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - B - 4 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - - B - 4 - - 36 - 1 - eighth - - down - 1 - begin - - - - - - - C - - none - - - - C - 1 - 5 - - 12 - 1 - 16th - down - 1 - end - backward hook - - - - - - - C - - none - - - - E - 5 - - 24 - 1 - eighth - down - 1 - begin - - - - - - - C - - none - - - - D - 5 - - 12 - 1 - 16th - down - 1 - end - backward hook - - - - - - - 6 - 1 - 32nd - 1 - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - - - - C - - none - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - 144 - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - sharp - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - A - 1 - 3 - - 6 - 5 - 32nd - sharp - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - sharp - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - B - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - - - - 21.00 - 0.00 - - 150.00 - - - 80.00 - - - - - C - - none - - - - B - 4 - - 12 - 1 - 16th - up - 1 - begin - begin - - - - - - - A - 4 - - 12 - 1 - 16th - up - 1 - end - end - - - - - - - 6 - 1 - 32nd - 1 - - - - A - 4 - - 6 - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - G - 1 - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - continue - - - - A - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - begin - begin - begin - - - - A - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - natural - up - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - - - - C - - none - - - - A - 1 - 4 - - 12 - 1 - 16th - sharp - up - 1 - begin - begin - - - - - - - B - 4 - - 12 - 1 - 16th - up - 1 - end - end - - - - - - - 6 - 1 - 32nd - 1 - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - A - 1 - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - 144 - - - - F - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - sharp - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - F - 1 - 3 - - 24 - 5 - eighth - down - 2 - - - - 24 - 5 - eighth - 2 - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - sharp - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - G - 1 - 3 - - 24 - 5 - eighth - down - 2 - - - - 24 - 5 - eighth - 2 - - - - - - - 21.00 - 0.00 - - 150.00 - - - 80.00 - - - - - C - - none - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 1 - 4 - - 6 - 1 - 32nd - sharp - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - C - - none - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 1 - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - C - - none - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - C - 1 - 5 - - 6 - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - B - 4 - - 6 - 1 - 32nd - natural - up - 1 - continue - continue - continue - - - - A - 1 - 4 - - 6 - 1 - 32nd - sharp - up - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - A - 4 - - 6 - 1 - 32nd - natural - up - 1 - begin - begin - begin - - - - G - 1 - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - continue - - - - F - 2 - 4 - - 6 - 1 - 32nd - double-sharp - up - 1 - continue - continue - continue - - - - G - 1 - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - F - 1 - 4 - - 6 - 1 - 32nd - sharp - up - 1 - begin - begin - begin - - - - E - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 1 - 32nd - sharp - up - 1 - continue - continue - continue - - - - E - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - - - 144 - - - - A - 3 - - 24 - 5 - eighth - down - 2 - begin - - - - - E - 4 - - 24 - 5 - eighth - down - 2 - - - - F - 1 - 3 - - 24 - 5 - eighth - down - 2 - continue - - - - - A - 3 - - 24 - 5 - eighth - down - 2 - - - - D - 3 - - 24 - 5 - eighth - down - 2 - end - - - - - B - 3 - - 24 - 5 - eighth - down - 2 - - - - E - 3 - - 24 - 5 - eighth - down - 2 - - - - - G - 1 - 3 - - 24 - 5 - eighth - down - 2 - - - - 24 - 5 - eighth - 2 - - - - 24 - 5 - eighth - 2 - - - - - - - 21.00 - 0.00 - - 70.00 - - - 82.79 - - - - - - - - - 1 - - - - - C - - none - - - - A - 4 - - 12 - 1 - 16th - down - 1 - begin - forward hook - - - - - C - 1 - 5 - - 12 - 1 - 16th - down - 1 - - - - A - 4 - - 24 - 1 - eighth - down - 1 - continue - - - - - C - 1 - 5 - - 24 - 1 - eighth - down - 1 - - - - A - 1 - 4 - - 12 - 1 - 16th - sharp - down - 1 - continue - begin - - - - - - - - C - 1 - 5 - - 12 - 1 - 16th - down - 1 - - - - B - 4 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - D - 5 - - 12 - 1 - 16th - down - 1 - - - - B - 1 - 4 - - 12 - 1 - 16th - sharp - down - 1 - end - end - - - - - D - 1 - 5 - - 12 - 1 - 16th - sharp - down - 1 - - - - C - - none - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - - - - A - 4 - - 6 - 1 - 32nd - natural - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - natural - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - natural - down - 1 - continue - continue - continue - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - G - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - B - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 6 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - 6 - 1 - 32nd - 1 - - - 144 - - - - A - 2 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - -

- - - 2 - - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 24 - 5 - eighth - down - 2 - - - - 24 - 5 - eighth - 2 - - - - 24 - 5 - eighth - 2 - - - - - - - 21.00 - -0.00 - - 150.00 - - - 80.00 - - - - - - - - - 1 - - - - - C - - none - - - - A - 4 - - 6 - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - C - 1 - 5 - - 6 - 1 - 32nd - up - 1 - - - - G - 1 - 4 - - 6 - 1 - 32nd - up - 1 - continue - end - end - - - - - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - - - - G - 1 - 4 - - 24 - 1 - eighth - up - 1 - continue - - - - - B - 4 - - 24 - 1 - eighth - up - 1 - - - - -

- - - 1 - - - - - G - 1 - 4 - - 12 - 1 - 16th - up - 1 - continue - begin - - - - - - - - B - 4 - - 12 - 1 - 16th - up - 1 - - - - A - 4 - - 12 - 1 - 16th - up - 1 - continue - continue - - - - - C - 5 - - 12 - 1 - 16th - natural - up - 1 - - - - A - 1 - 4 - - 12 - 1 - 16th - sharp - up - 1 - end - end - - - - - C - 1 - 5 - - 12 - 1 - 16th - sharp - up - 1 - - - - C - - none - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - G - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - natural - down - 1 - end - end - end - - - - B - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - C - 1 - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 6 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - 6 - 1 - 32nd - 1 - - - 144 - - - - E - 2 - - 6 - 5 - 32nd - up - 2 - begin - begin - begin - - - - - - - E - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - - - - E - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - up - 2 - end - end - end - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - E - 3 - - 24 - 5 - eighth - down - 2 - - - - 24 - 5 - eighth - 2 - - - - 24 - 5 - eighth - 2 - - - - - - - 21.00 - -0.00 - - 150.00 - - - 80.00 - - - - - C - - none - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - A - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - end - - - - - - - A - 4 - - 12 - - 1 - 16th - up - 1 - end - end - - - - - - - A - 4 - - 6 - - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - A - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - C - - none - - - - A - 1 - 4 - - 6 - 1 - 32nd - sharp - down - 1 - begin - begin - begin - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - B - 1 - 4 - - 6 - 1 - 32nd - sharp - down - 1 - begin - begin - begin - - - - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - A - 5 - - 6 - 1 - 32nd - natural - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - C - - none - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - natural - down - 1 - end - end - end - - - - - - 144 - - - - 24 - 5 - eighth - 2 - - - - F - 1 - 3 - - 24 - 5 - eighth - down - 2 - begin - - - - - E - 4 - - 24 - 5 - eighth - down - 2 - - - - G - 1 - 3 - - 24 - 5 - eighth - down - 2 - end - - - - - E - 4 - - 24 - 5 - eighth - down - 2 - - - - A - 3 - - 24 - 5 - eighth - down - 2 - - - - - E - 4 - - 24 - 5 - eighth - down - 2 - - - - 24 - 5 - eighth - 2 - - - - D - 3 - - 24 - 5 - eighth - down - 2 - - - - - F - 1 - 3 - - 24 - 5 - eighth - down - 2 - - - - - B - 3 - - 24 - 5 - eighth - down - 2 - - - - - - - 21.00 - -0.00 - - 150.00 - - - 80.00 - - - - - - - - C - - none - - - - E - 5 - - 18 - 1 - 16th - - down - 1 - begin - begin - - - - - - - - F - 1 - 5 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - E - 5 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - D - 1 - 5 - - 1 - 32nd - sharp - up - 1 - continue - continue - continue - - - - - E - 5 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - backward hook - - - - - - - C - - none - - - - A - 4 - - 36 - 1 - eighth - - up - 1 - begin - - - - - - - C - - none - - - - C - 1 - 5 - - 6 - 1 - 32nd - up - 1 - continue - begin - begin - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - - - - C - - none - - - - G - 1 - 4 - - 24 - 1 - eighth - up - 1 - begin - - - - - - - - B - 4 - - 24 - 1 - eighth - up - 1 - - - - C - - none - - - - - - - - A - 4 - - 12 - 1 - 16th - up - 1 - - - - C - 4 - - 9 - 1 - 32nd - - 1 - - - - E - 4 - - 3 - 1 - 64th - up - 1 - end - end - backward hook - backward hook - - - - F - 1 - 4 - - 3 - 1 - 64th - up - 1 - begin - begin - begin - begin - - - - - - - A - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - G - 1 - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - B - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - A - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - C - 1 - 5 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - B - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - D - 5 - - 3 - 1 - 64th - natural - up - 1 - end - end - end - end - - - - - - - - 96 - - - - - A - 4 - - 36 - 2 - eighth - - down - 1 - begin - - - - G - 1 - 4 - - 12 - 2 - 16th - down - 1 - end - backward hook - - - - E - 4 - - 48 - 2 - quarter - down - 1 - - - 120 - - - - A - 3 - - 48 - 5 - quarter - up - 2 - - - - - C - 1 - 4 - - 48 - 5 - quarter - up - 2 - - - - B - 3 - - 24 - 5 - eighth - up - 2 - - - - - D - 4 - - 24 - 5 - eighth - up - 2 - - - - D - 4 - - 24 - 5 - eighth - up - 2 - begin - - - - - - - C - 1 - 4 - - 24 - 5 - eighth - up - 2 - end - - - - - - - G - 2 - - 24 - 5 - eighth - 2 - - - 144 - - - - E - 3 - - 72 - 6 - quarter - - down - 2 - - - - A - 3 - - 48 - 6 - quarter - down - 2 - - - 24 - - - light-heavy - - - - - - - - - 21.00 - -0.00 - - 110.60 - - - 80.00 - - - - - - - - C - - none - - - - E - 5 - - 18 - 1 - 16th - - down - 1 - begin - begin - - - - - - - - F - 1 - 5 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - E - 5 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - D - 1 - 5 - - 1 - 32nd - sharp - up - 1 - continue - continue - continue - - - - - E - 5 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - backward hook - - - - - - - C - - none - - - - A - 4 - - 36 - 1 - eighth - - up - 1 - begin - - - - - - - C - - none - - - - C - 1 - 5 - - 6 - 1 - 32nd - up - 1 - continue - begin - begin - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - - - - C - - none - - - - - E - 4 - - 24 - 1 - eighth - up - 1 - begin - - - - - - - - G - 1 - 4 - - 24 - 1 - eighth - up - 1 - - - - - B - 4 - - 24 - 1 - eighth - up - 1 - - - - C - - none - - - - A - 4 - - 18 - 1 - 16th - - up - 1 - continue - begin - - - - - - - - B - 4 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - A - 4 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - G - 1 - 4 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - A - 4 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - forward hook - - - - C - 1 - 5 - - 18 - 1 - 16th - - up - 1 - continue - continue - - - - - D - 5 - - 1 - 32nd - natural - up - 1 - begin - begin - begin - - - - - - - - C - 1 - 5 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - B - 4 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - C - 1 - 5 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - D - 5 - - 6 - 1 - 32nd - up - 1 - end - end - backward hook - - - 144 - - - - - 96 - - - - A - 4 - - 36 - 2 - eighth - - down - 1 - begin - - - - G - 1 - 4 - - 12 - 2 - 16th - down - 1 - end - backward hook - - - 72 - - - - A - 3 - - 48 - 5 - quarter - up - 2 - - - - - C - 1 - 4 - - 48 - 5 - quarter - up - 2 - - - - B - 3 - - 24 - 5 - eighth - up - 2 - - - - - D - 4 - - 24 - 5 - eighth - up - 2 - - - - D - 4 - - 24 - 5 - eighth - up - 2 - begin - - - - - - - C - 1 - 4 - - 24 - 5 - eighth - up - 2 - end - - - - - - - 24 - 5 - eighth - 2 - - - 144 - - - - E - 3 - - 72 - 6 - quarter - - down - 2 - - - - A - 3 - - 48 - 6 - quarter - down - 2 - - - 24 - - - light-heavy - - - - - - - - 21.00 - -0.00 - - 150.00 - - - 80.00 - - - - heavy-light - - - - - G - 2 - - - - - C - - none - - - - E - 5 - - 18 - 1 - 16th - - down - 1 - begin - begin - - - - - - - F - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - begin - begin - - - - E - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - end - end - - - - C - - none - - - - D - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - C - 1 - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - C - - none - - - - B - 4 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - A - 4 - - 12 - 1 - 16th - down - 1 - end - end - - - - C - - none - - - - A - 4 - - 18 - 1 - 16th - - down - 1 - begin - begin - - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - end - forward hook - - - - - - - C - - none - - - - F - 1 - 5 - - 24 - 1 - eighth - down - 1 - end - - - - C - - none - - - - D - 4 - - 24 - - 1 - eighth - up - 1 - - - - - - 144 - - - - A - 3 - - 6 - 5 - 32nd - up - 2 - begin - begin - begin - - - - - - - A - 4 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - C - 1 - 4 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - A - 4 - - 6 - 5 - 32nd - up - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - up - 2 - begin - begin - begin - - - - G - 1 - 4 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - B - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - G - 1 - 4 - - 6 - 5 - 32nd - up - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - up - 2 - begin - begin - begin - - - - G - 4 - - 6 - 5 - 32nd - natural - up - 2 - continue - continue - continue - - - - C - 1 - 4 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - G - 4 - - 6 - 5 - 32nd - up - 2 - end - end - end - - - - - - - A - 3 - - 6 - 5 - 32nd - up - 2 - begin - begin - begin - - - - - - - F - 1 - 4 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - D - 4 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - F - 1 - 4 - - 6 - 5 - 32nd - up - 2 - end - end - end - - - - - - - F - 4 - - - - - simile - - 2 - - - - F - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - D - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - D - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - F - 1 - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - 21.00 - 0.00 - - 150.00 - - - 96.79 - - - - - C - - none - - - - D - 4 - - 3 - - 1 - 64th - up - 1 - begin - begin - begin - begin - - - - - - - - F - 1 - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - E - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - D - 4 - - 3 - 1 - 64th - up - 1 - end - end - end - end - - - - E - 4 - - 3 - 1 - 64th - up - 1 - begin - begin - begin - begin - - - - F - 1 - 4 - - 3 - 1 - 64th - up - 1 - continue - continue - continue - continue - - - - G - 4 - - 3 - 1 - 64th - natural - up - 1 - continue - continue - continue - continue - - - - A - 4 - - 3 - 1 - 64th - up - 1 - end - end - end - end - - - - B - 4 - - 3 - 1 - 64th - down - 1 - begin - begin - begin - begin - - - - C - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - D - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - E - 5 - - 3 - 1 - 64th - down - 1 - end - end - end - end - - - - F - 1 - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - begin - begin - begin - - - - - - - - G - 5 - - 4 - 1 - 32nd - natural - - 3 - 2 - - down - 1 - continue - continue - continue - - - - A - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - end - end - end - - - - - - - B - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - C - 1 - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 6 - - 9 - 1 - 32nd - - down - 1 - continue - continue - continue - - - - F - 1 - 5 - - 3 - 1 - 64th - down - 1 - end - end - end - backward hook - - - - A - 5 - - 18 - 1 - 16th - - down - 1 - begin - begin - - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - forward hook - - - - - - - C - - none - - - - E - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - E - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - E - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - E - 5 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - 144 - - - - A - 2 - - 6 - 5 - 32nd - up - 2 - begin - begin - begin - - - - F - 1 - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - D - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - F - 1 - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - A - 2 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - F - 1 - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - D - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - F - 1 - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - A - 2 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - F - 1 - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - D - 3 - - 6 - 5 - 32nd - up - 2 - continue - continue - continue - - - - F - 1 - 3 - - 6 - 5 - 32nd - up - 2 - end - end - end - - - - A - 2 - - 24 - 5 - eighth - up - 2 - begin - - - - - - - - D - 3 - - 24 - 5 - eighth - up - 2 - - - - A - 2 - - 24 - 5 - eighth - up - 2 - end - - - - - - - - C - 1 - 3 - - 24 - 5 - eighth - up - 2 - - - - 24 - 5 - eighth - 2 - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - - - - - 1 - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - A - 4 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - E - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - E - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - E - 5 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - - C - - none - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - end - - - - G - 1 - 4 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - E - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - E - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - E - 5 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - 144 - - - - A - 3 - - 72 - 5 - quarter - - down - 2 - - - - - C - 1 - 4 - - 72 - 5 - quarter - - down - 2 - - - - - E - 4 - - 72 - 5 - quarter - - down - 2 - - - - - - - - 2 - - - - - B - 3 - - 72 - 5 - quarter - - down - 2 - - - - - D - 4 - - 72 - 5 - quarter - - down - 2 - - - - - E - 4 - - 72 - 5 - quarter - - down - 2 - - - - - - - 21.00 - -0.00 - - 150.00 - - - 86.86 - - - - - C - - none - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - C - 1 - 5 - - 24 - 1 - eighth - up - 1 - begin - - - - - - - C - - none - - - - B - 4 - - 6 - 1 - 32nd - up - 1 - continue - begin - begin - - - - - - - E - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - continue - - - - - - - E - 5 - - 6 - 1 - 32nd - up - 1 - continue - continue - continue - - - - E - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - D - 1 - 5 - - 6 - 1 - 32nd - sharp - up - 1 - begin - begin - begin - - - - E - 4 - - 6 - 1 - 32nd - up - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - natural - up - 1 - continue - continue - continue - - - - E - 4 - - 6 - 1 - 32nd - up - 1 - end - end - end - - - - - - 144 - - - - - - - - 2 - - - - - A - 3 - - 24 - 5 - eighth - down - 2 - begin - - - - - C - 1 - 4 - - 24 - 5 - eighth - down - 2 - - - - - E - 4 - - 24 - 5 - eighth - down - 2 - - - - - - - - 2 - - - - - G - 1 - 3 - - 24 - 5 - eighth - down - 2 - continue - - - - - B - 3 - - 24 - 5 - eighth - down - 2 - - - - - E - 4 - - 24 - 5 - eighth - down - 2 - - - - - - - - 2 - - - - - A - 3 - - 24 - 5 - eighth - down - 2 - end - - - - - C - 1 - 4 - - 24 - 5 - eighth - down - 2 - - - - - E - 4 - - 24 - 5 - eighth - down - 2 - - - - A - 3 - - 24 - 5 - eighth - up - 2 - begin - - - - - - - - C - 1 - 4 - - 24 - 5 - eighth - up - 2 - - - - G - 1 - 3 - - 24 - 5 - eighth - up - 2 - end - - - - - - - - B - 3 - - 24 - 5 - eighth - up - 2 - - - - 24 - 5 - eighth - 2 - - - 144 - - - 72 - - - - E - 3 - - 48 - 6 - quarter - down - 2 - - - 24 - - - - - - - 21.00 - 0.00 - - 70.00 - - - 80.00 - - - - - C - - none - - - - C - 1 - 5 - - 24 - 1 - eighth - down - 1 - begin - - - - C - 1 - 5 - - 36 - 1 - eighth - - down - 1 - continue - - - - - - - C - - none - - - - - D - 5 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - C - 1 - 5 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - B - 4 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - C - 1 - 5 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - D - 5 - - 12 - 1 - 16th - down - 1 - end - backward hook - - - - - - - C - - none - - - - F - 1 - 5 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - end - end - backward hook - - - - - - - 6 - 1 - 32nd - 1 - - - - D - 1 - 6 - - 12 - 1 - 16th - sharp - down - 1 - begin - begin - - - - - - - E - 6 - - 6 - 1 - 32nd - down - 1 - end - end - backward hook - - - - - - - 3 - 1 - 64th - 1 - - - - E - 5 - - 3 - 1 - 64th - down - 1 - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - 144 - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - B - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - C - 1 - 4 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - - - - 21.00 - -0.00 - - 150.00 - - - 99.20 - - - - - C - - none - - - - C - 1 - 5 - - 12 - 1 - 16th - down - 1 - begin - forward hook - - - - - - - B - 4 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - B - 4 - - 24 - 1 - eighth - down - 1 - continue - - - - C - - none - - - - - C - 1 - 5 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - B - 4 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - A - 1 - 4 - - 1 - 32nd - sharp - up - 1 - end - end - end - - - - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - continue - begin - begin - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - C - - none - - - - E - 5 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - end - end - backward hook - - - - - - - 6 - 1 - 32nd - 1 - - - - C - - none - - - - C - 1 - 6 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - D - 6 - - 6 - 1 - 32nd - down - 1 - end - end - backward hook - - - - - - - 3 - 1 - 64th - 1 - - - - D - 5 - - 3 - 1 - 64th - down - 1 - - - - C - - none - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - 144 - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - sharp - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - B - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - sharp - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - B - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - - - - 21.00 - -0.00 - - 150.00 - - - 106.07 - - - - - C - - none - - - - B - 4 - - 12 - 1 - 16th - up - 1 - begin - begin - - - - - - - A - 4 - - 12 - - 1 - 16th - up - 1 - end - end - - - - - - - - A - 4 - - 6 - - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - A - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - D - 5 - - 6 - 1 - 32nd - natural - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - A - 1 - 4 - - 6 - 1 - 32nd - sharp - down - 1 - begin - begin - begin - - - - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - B - 1 - 4 - - 6 - 1 - 32nd - sharp - down - 1 - begin - begin - begin - - - - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - natural - down - 1 - continue - continue - continue - - - - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - natural - down - 1 - end - end - end - - - - - - 144 - - - - F - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - sharp - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - F - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - D - 1 - 4 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 4 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - - - - A - 3 - - 24 - 5 - eighth - down - 2 - - - - 24 - 5 - eighth - 2 - - - - D - 3 - - 24 - 5 - eighth - natural - down - 2 - - - - - F - 1 - 3 - - 24 - 5 - eighth - down - 2 - - - - - B - 3 - - 24 - 5 - eighth - natural - down - 2 - - - - - - - 21.00 - -0.00 - - 150.00 - - - 101.86 - - - - - C - - none - - - - E - 5 - - 18 - 1 - 16th - - down - 1 - begin - begin - - - - - - - - F - 1 - 5 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - E - 5 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - D - 1 - 5 - - 1 - 32nd - sharp - up - 1 - continue - continue - continue - - - - - E - 5 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - F - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - backward hook - - - - - - - C - - none - - - - A - 4 - - 36 - 1 - eighth - - up - 1 - begin - - - - - - - C - - none - - - - B - 4 - - 12 - 1 - 16th - up - 1 - end - backward hook - - - - - - - C - - none - - - - - - - - 1 - - - - - B - 4 - - 36 - 1 - eighth - - up - 1 - begin - - - - - - - B - 1 - 4 - - 12 - 1 - 16th - sharp - up - 1 - continue - forward hook - - - - C - - none - - - - C - 1 - 5 - - 24 - 1 - eighth - up - 1 - end - - - - - - 144 - - - 72 - - - - G - 1 - 4 - - 48 - 2 - quarter - down - 1 - - - - - - - A - 4 - - 24 - 2 - eighth - down - 1 - - - - - - 144 - - - - A - 3 - - 48 - 5 - quarter - up - 2 - - - - - C - 1 - 4 - - 48 - 5 - quarter - up - 2 - - - - B - 3 - - 24 - 5 - eighth - up - 2 - - - - - D - 4 - - 24 - 5 - eighth - up - 2 - - - - D - 4 - - 48 - 5 - quarter - up - 2 - - - - - - - C - 1 - 4 - - 24 - 5 - eighth - up - 2 - - - - - - 144 - - - - E - 3 - - 72 - 6 - quarter - - down - 2 - - - - A - 3 - - 72 - 6 - quarter - - down - 2 - - - - - E - 4 - - 72 - 6 - quarter - - down - 2 - - - - - - - 21.00 - 0.00 - - 91.47 - - - 80.11 - - - - - C - - none - - - - - - - - 1 - - - - - - E - 4 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - A - 4 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - C - 1 - 5 - - 24 - - 1 - eighth - down - 1 - begin - - - - - - - C - 1 - 5 - - 4 - - 1 - 32nd - - 3 - 2 - - down - 1 - continue - begin - begin - - - - - - - - E - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - continue - continue - continue - - - - - - - D - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - end - end - end - - - - - - - C - 1 - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - begin - begin - begin - - - - - - - D - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - continue - continue - continue - - - - E - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - end - end - end - - - - - - - C - - none - - - - D - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - begin - begin - begin - - - - - - - F - 1 - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - continue - continue - continue - - - - E - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - end - end - end - - - - - - - D - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - begin - begin - begin - - - - - - - E - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - continue - continue - continue - - - - F - 1 - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - end - end - end - - - - - - - - C - - none - - - - E - 5 - - 3 - 1 - 64th - down - 1 - begin - begin - begin - begin - - - - A - 4 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - - - - B - 4 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - C - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - D - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - E - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - F - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - A - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - B - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - C - 1 - 6 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - B - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - A - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - F - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - E - 1 - 5 - - 3 - 1 - 64th - sharp - down - 1 - continue - continue - continue - continue - - - - C - - none - - - - F - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - A - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - B - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - A - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - F - 1 - 5 - - 3 - 1 - 64th - down - 1 - end - end - end - end - - - 144 - - - - A - 2 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - - - - C - 1 - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 2 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - C - 1 - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - E - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - A - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - B - 2 - - 6 - 5 - 32nd - down - 2 - begin - begin - begin - - - - D - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - G - 1 - 3 - - 6 - 5 - 32nd - down - 2 - continue - continue - continue - - - - B - 3 - - 6 - 5 - 32nd - down - 2 - end - end - end - - - - A - 3 - - 72 - 5 - quarter - - up - 2 - - - - - - 144 - - - 72 - - - - C - 1 - 3 - - 48 - 6 - quarter - down - 2 - - - - D - 3 - - 24 - 6 - eighth - down - 2 - - - - - - - 21.00 - -0.00 - - 150.00 - - - 80.00 - - - - - - - - -

- - - 1 - - - - - C - - none - - - - E - 5 - - 6 - 1 - 32nd - natural - down - 1 - begin - begin - begin - - - - - - - E - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - C - 1 - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - C - - none - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - B - 4 - - 24 - 1 - eighth - up - 1 - begin - - - - - - - C - - none - - - - A - 4 - - 4 - 1 - 32nd - - 3 - 2 - - up - 1 - continue - begin - begin - - - - - - - - E - 4 - - 4 - 1 - 32nd - - 3 - 2 - - up - 1 - continue - continue - continue - - - - F - 1 - 4 - - 4 - 1 - 32nd - - 3 - 2 - - up - 1 - end - end - end - - - - - - - G - 4 - - 4 - 1 - 32nd - natural - - 3 - 2 - - up - 1 - begin - begin - begin - - - - - - - G - 1 - 4 - - 4 - 1 - 32nd - sharp - - 3 - 2 - - up - 1 - continue - continue - continue - - - - A - 4 - - 4 - 1 - 32nd - - 3 - 2 - - up - 1 - end - end - end - - - - - - - A - 1 - 4 - - 4 - 1 - 32nd - sharp - - 3 - 2 - - up - 1 - begin - begin - begin - - - - - - - B - 4 - - 4 - 1 - 32nd - - 3 - 2 - - up - 1 - continue - continue - continue - - - - B - 1 - 4 - - 4 - 1 - 32nd - sharp - - 3 - 2 - - up - 1 - end - end - end - - - - - - - C - 1 - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - begin - begin - begin - - - - - - - D - 5 - - 4 - 1 - 32nd - - 3 - 2 - - down - 1 - continue - continue - continue - - - - D - 1 - 5 - - 4 - 1 - 32nd - sharp - - 3 - 2 - - down - 1 - end - end - end - - - - - - 144 - - - 72 - - - - G - 1 - 4 - - 24 - 2 - eighth - down - 1 - - - 96 - - - - E - 3 - - 24 - 5 - eighth - down - 2 - begin - - - - - C - 1 - 4 - - 24 - 5 - eighth - down - 2 - - - - E - 3 - - 24 - 5 - eighth - down - 2 - continue - - - - - C - 1 - 4 - - 24 - 5 - eighth - down - 2 - - - - E - 3 - - 24 - 5 - eighth - down - 2 - end - - - - - G - 1 - 3 - - 24 - 5 - eighth - down - 2 - - - - - D - 4 - - 24 - 5 - eighth - down - 2 - - - - D - 4 - - 24 - 5 - eighth - up - 2 - begin - - - - - - - C - 1 - 4 - - 24 - 5 - eighth - up - 2 - end - - - - - - - G - 2 - - 24 - 5 - eighth - 2 - - - 144 - - - 72 - - - - A - 3 - - 48 - 6 - quarter - down - 2 - - - 24 - - - light-heavy - - - - - - - - - 21.00 - 0.00 - - 150.00 - - - 80.00 - - - - - - - - -

- - - 1 - - - - - C - - none - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - E - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - C - 1 - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - E - 5 - - 6 - 1 - 32nd - down - 1 - begin - begin - begin - - - - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - 4 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - G - 1 - 4 - - 48 - 1 - quarter - up - 1 - - - - - - - - - B - 4 - - 48 - 1 - quarter - up - 1 - - - - C - - none - - - - A - 4 - - 24 - 1 - eighth - up - 1 - - - - - - - 144 - - - 72 - - - - E - 4 - - 72 - 2 - quarter - - down - 1 - - - 144 - - - - E - 3 - - 24 - 5 - eighth - down - 2 - begin - - - - - C - 1 - 4 - - 24 - 5 - eighth - down - 2 - - - - E - 3 - - 24 - 5 - eighth - down - 2 - continue - - - - - C - 1 - 4 - - 24 - 5 - eighth - down - 2 - - - - E - 3 - - 24 - 5 - eighth - down - 2 - end - - - - - G - 1 - 3 - - 24 - 5 - eighth - down - 2 - - - - - D - 4 - - 24 - 5 - eighth - down - 2 - - - - D - 4 - - 48 - 5 - quarter - up - 2 - - - - - - - C - 1 - 4 - - 24 - 5 - eighth - up - 2 - - - - - - 144 - - - 72 - - - - A - 3 - - 72 - 6 - quarter - - down - 2 - - - light-light - - - - - diff --git a/tests/data/musicxml/test_partial_measures.xml b/tests/data/musicxml/test_partial_measures.xml new file mode 100644 index 00000000..9a10a304 --- /dev/null +++ b/tests/data/musicxml/test_partial_measures.xml @@ -0,0 +1,148 @@ + + + + + + MusicXML Part + + + + + + + 1 + + 0 + + + 1 + + G + 2 + + + + + C + 4 + + 2 + 1 + half + + + + C + 4 + + 2 + 1 + half + + + + + + + D + 4 + + 1 + 1 + quarter + + + + E + 4 + + 1 + 1 + quarter + + + + + + + + + + F + 4 + + 2 + 1 + half + + + light-heavy + + + + + + + + + + + + G + 4 + + 4 + 1 + whole + + + + + + + + + + A + 4 + + 2 + 1 + half + + + + + + + B + -1 + 4 + + 1 + 1 + quarter + + + + + + + F + 4 + + 2 + 1 + half + + + light-heavy + + + + diff --git a/tests/data/musicxml/test_partial_measures_consecutive.xml b/tests/data/musicxml/test_partial_measures_consecutive.xml index d825b9db..c535303a 100644 --- a/tests/data/musicxml/test_partial_measures_consecutive.xml +++ b/tests/data/musicxml/test_partial_measures_consecutive.xml @@ -1,2889 +1,134 @@ - - - - - K333 - Piano Sonata no. 13 in B flat major - - 3 - Allegretto grazioso - - Wolfgang Amadeus Mozart - - MuseScore 3.3.4 - 2021-04-27 - - - - - - - https://kern.humdrum.org/cgi-bin/ksdata?location=users/craig/classical/mozart/piano/sonata&file=sonata13-3.krn&format=xml - - - - 7.05556 - 40 - - - 1683.78 - 1190.55 - - 56.6929 - 56.6929 - 56.6929 - 113.386 - - - 56.6929 - 56.6929 - 56.6929 - 113.386 - - - - - + + + - Piano, Piano right - Pno. - - Piano - - - - 1 - 1 - 78.7402 - 0 - - - + MusicXML Part + + - - - - - 21.00 - 0.00 - - 71.20 - - - 80.00 - - + + - 48 + 1 - -2 - - - 2 - + 0 + + + 1 + G 2 - - - G - 2 - - - - - -

- - - 1 - - - - - C - - none - - - - F - 5 - - 72 - 1 - quarter - - down - 1 - - - - - - - D - 5 - - 24 - 1 - eighth - down - 1 - - - - - - - C - - none - - - - B - -1 - 4 - - 48 - 1 - quarter - down - 1 - - - - C - - none - - - - B - -1 - 4 - - 48 - 1 - quarter - down - 1 - - - 192 - - - - 48 - 5 - quarter - 2 - - - - -

- - - 2 - - - - - B - -1 - 3 - - 48 - 5 - quarter - up - 2 - - - - D - 4 - - 48 - 5 - quarter - up - 2 - - - - - - - - - G - 4 - - 48 - 5 - quarter - up - 2 - - - - - - - - - - - - 21.00 - 0.00 - - 142.07 - - - 80.00 - - - - - C - - none - - - - D - -1 - 6 - - 72 - 1 - quarter - - flat - down - 1 - - - - B - -1 - 5 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - begin - - - - B - -1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - - C - - none - - - - D - -1 - 6 - - 72 - 1 - quarter - - down - 1 - - - - B - -1 - 5 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - begin - - - - B - -1 - 5 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - 192 - - - - B - -1 - 2 - - 96 - 5 - half - down - 2 - - - - - B - -1 - 3 - - 96 - 5 - half - down - 2 - - - - G - -1 - 2 - - 96 - 5 - half - flat - up - 2 - - - - - G - -1 - 3 - - 96 - 5 - half - flat - up - 2 - - - - - - C - - none - - - - D - -1 - 6 - - 96 - - 1 - half - flat - down - 1 - - - - - - - - C - - none - - - - D - -1 - 6 - - 12 - - 1 - 16th - down - 1 - begin - begin - - - - - - - B - -1 - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - G - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - E - 5 - - 12 - 1 - 16th - natural - down - 1 - end - end - - - - D - -1 - 5 - - 12 - 1 - 16th - flat - up - 1 - begin - begin - - - - B - -1 - 4 - - 12 - 1 - 16th - up - 1 - continue - continue - - - - G - 4 - - 12 - 1 - 16th - up - 1 - continue - continue - - + + + - E + C 4 - - 12 - 1 - 16th - natural - up - 1 - end - end - - - 192 - - - - E - 2 - - 96 - 5 - half - natural - up - 2 - - - - - - - - E - 3 - - 96 - 5 - half - natural - up - 2 - - - - ad libitum - - 2 - - - - 48 - 5 - quarter - 2 - - - - 48 - 5 - quarter - 2 - - - none - - - - - - 48 - 1 - quarter - 1 - - - - 48 - 1 - quarter - 1 - - - - F - 3 - - 96 + + 2 1 half - up - 2 - - - - - - - 192 - - - - D - -1 - 4 - - 12 - 5 - 16th - flat - up - 2 - begin - begin - - - - B - -1 - 3 - - 12 - 5 - 16th - up - 2 - continue - continue - - - - G - 3 - - 12 - 5 - 16th - up - 2 - continue - continue - - - - E - 3 - - 12 - 5 - 16th - natural - up - 2 - end - end - - - - D - -1 - 3 - - 12 - 5 - 16th - flat - up - 2 - begin - begin - - - - B - -1 - 2 - - 12 - 5 - 16th - up - 2 - continue - continue - - - - G - 2 - - 12 - 5 - 16th - up - 2 - end - end - - - - 12 - 5 - 16th - 2 - - - - F - 1 - - 96 - 5 - half - down - 2 - - - - - - - - F - 2 - - 96 - 5 - half - down - 2 - - - 192 - - - 84 - - - - E - 2 - - 24 - 6 - eighth - natural - down - 2 - - - 84 - - - none - - - - - - - 21.00 - 0.00 - - 142.07 - - - 85.40 - - - - - F - 3 - - 3 - 1 - 64th - down - 1 - begin - begin - begin - begin - - - - - - - - A - 3 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 3 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - F - 3 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 3 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - A - 3 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - B - -1 - 3 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - + + C 4 - - 3 + + 2 1 - 64th - down - 1 - continue - continue - continue - continue - - + half + + + + + D 4 - - 3 + + 1 1 - 64th - down - 1 - continue - continue - continue - continue - - + half + + E - -1 4 - - 3 + + 1 1 - 64th - down - 1 - continue - continue - continue - continue - - + half + + + + + F 4 - - 3 + + 2 1 - 64th - down - 1 - continue - continue - continue - continue - - + whole + + + + + G 4 - - 3 + + 4 1 - 64th - down - 1 - continue - continue - continue - continue - - + half + + + + + A 4 - - 3 + + 2 1 - 64th - down - 1 - continue - continue - continue - continue - - + half + + + + + B -1 4 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - C - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - D - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - - - - E - -1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - - - - F - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - G - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - A - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - B - -1 - 5 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - C - 6 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - D - 6 - - 3 - 1 - 64th - down - 1 - continue - continue - continue - continue - - - - E - -1 - 6 - - 3 + + 1 1 - 64th - down - 1 - continue - end - end - end - - + quarter + + + + + F - 6 - - 24 - 1 - eighth - down - 1 - end - - - - - - - 18 - 1 - 16th - - 1 - - - - - - - B - -1 - 4 - - 6 - 1 - 32nd - down - 1 - - - - - B - -1 4 - - 1 - 32nd - up - 1 - begin - begin - begin - - - - - - - - C - 5 - - 1 - 32nd - up - 1 - continue - continue - continue - - - - - D - 5 - - 1 - 32nd - up - 1 - end - end - end - - - - - - - C - 5 - - 192 + + 2 1 whole - 1 - - - - - - - - - B - 4 - - 6 - 1 - 32nd - natural - down - 1 - begin - begin - begin - - - - - - - C - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - E - -1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - F - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - G - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - A - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - B - -1 - 5 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - C - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - E - -1 - 6 - - 6 - 1 - 32nd - down - 1 - continue - continue - continue - - - - D - 6 - - 6 - 1 - 32nd - down - 1 - end - end - end - - - - - - 384 - - - - 192 - 5 - whole - 2 - - - - 96 - 5 - half - 2 - - - - F - 3 - - 96 - - 5 - half - down - 2 - - - - - - - - A - 3 - - 96 - - 5 - half - down - 2 - - - - - - - - C - 4 - - 96 - - 5 - half - down - 2 - - - - - - - - E - -1 - 4 - - 96 - - 5 - half - down - 2 - - - - + - none + light-heavy - - - - - - 21.00 - 0.00 - - 142.07 - - - 80.00 - - - - - F - 6 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - E - -1 - 6 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - D - 6 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - - - C - 6 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - - - - E - -1 - 6 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - D - 6 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - C - 6 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - - - B - -1 - 5 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - - - - D - 6 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - C - 6 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - B - -1 - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - - - A - 5 - - 12 - 1 - 16th - down - 1 - end - end - - - - - - - - - C - 6 - - 12 - 1 - 16th - down - 1 - begin - begin - - - - - - - B - -1 - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - A - 5 - - 12 - 1 - 16th - down - 1 - continue - continue - - - - - - - - - G - 5 - - 12 - 1 - 16th - down - 1 - continue - end - - - - - - - - - B - -1 - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - A - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - G - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - - - F - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - - - A - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - G - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - F - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - - - E - -1 - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - - - G - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - F - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - E - -1 - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - - - D - 5 - - 24 - 1 - eighth - down - 1 - end - - - - - - - - - F - 5 - - 48 - 1 - quarter - down - 1 - - - - - - - E - -1 - 5 - - 48 - 1 - quarter - down - 1 - - - - - - - D - 5 - - 48 - 1 - quarter - down - 1 - - - - - - - - - C - 5 - - 48 - 1 - quarter - down - 1 - - - - - - - - - B - 4 - - 24 - 1 - eighth - natural - down - 1 - begin - - - - - - - - - C - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - - - D - -1 - 5 - - 24 - 1 - eighth - flat - down - 1 - continue - - - - - - - - - D - 5 - - 24 - 1 - eighth - natural - down - 1 - continue - - - - - - - - - E - -1 - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - E - 5 - - 24 - 1 - eighth - natural - down - 1 - end - - - - - - 816 - - - - F - 3 - - 48 - - 5 - quarter - down - 2 - - - - - - - - A - 3 - - 48 - - 5 - quarter - down - 2 - - - - - - - - C - 4 - - 48 - - 5 - quarter - down - 2 - - - - - - - - E - -1 - 4 - - 48 - - 5 - quarter - down - 2 - - - - - - - 48 - 5 - quarter - 2 - - - - 96 - 5 - half - 2 - - - - 192 - 5 - whole - 2 - - - - 192 - 5 - whole - 2 - - - - 96 - 5 - half - 2 - - - - 96 - 5 - half - 2 - - - - 48 - 5 - quarter - 2 - - - - - - - 21.00 - 0.00 - - 142.07 - - - 80.00 - - - - - G - 2 - - - - - C - - none - - - - -

- - - 1 - - - - - in - tempo - - 1 - - - - F - 5 - - 72 - 1 - quarter - - down - 1 - - - - - - - D - 5 - - 24 - 1 - eighth - down - 1 - - - - - - - C - - none - - - - B - -1 - 4 - - 48 - 1 - quarter - down - 1 - - - - C - - none - - - - B - -1 - 4 - - 48 - 1 - quarter - down - 1 - - - 192 - - - - 48 - 5 - quarter - 2 - - - - -

- - - 2 - - - - - B - -1 - 3 - - 48 - 5 - quarter - up - 2 - - - - D - 4 - - 48 - 5 - quarter - up - 2 - - - - G - 4 - - 48 - 5 - quarter - up - 2 - - - - - - C - - none - - - - E - -1 - 5 - - 48 - 1 - quarter - down - 1 - - - - - - - G - 5 - - 48 - 1 - quarter - down - 1 - - - - - - - C - - none - - - - A - 4 - - 48 - 1 - quarter - up - 1 - - - - C - - none - - - - 24 - 1 - eighth - 1 - - - - A - 4 - - 24 - 1 - eighth - up - 1 - - - 192 - - - - C - 4 - - 48 - 5 - quarter - up - 2 - - - - - - - E - -1 - 4 - - 48 - 5 - quarter - up - 2 - - - - - - - F - 4 - - 48 - 5 - quarter - up - 2 - - - - E - -1 - 4 - - 48 - 5 - quarter - up - 2 - - - - - - C - - none - - - - C - 5 - - 24 - 1 - eighth - down - 1 - begin - - - - - - - B - -1 - 4 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - C - - none - - - - D - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - C - 5 - - 24 - 1 - eighth - down - 1 - end - - - - - - - C - - none - - - - E - -1 - 5 - - 24 - 1 - eighth - down - 1 - begin - - - - - - - D - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - C - - none - - - - F - 5 - - 24 - 1 - eighth - down - 1 - continue - - - - - - - E - -1 - 5 - - 24 - 1 - eighth - down - 1 - end - - - - - - 192 - - - - D - 4 - - 48 - 5 - quarter - up - 2 - - - - F - 4 - - - - - F - 4 - - 96 - 5 - half - up - 2 - - - - C - 4 - - 48 - 5 - quarter - up - 2 - - - 192 - - - 48 - - - - A - 3 - - 48 - 6 - quarter - down - 2 - - - - B - -1 - 3 - - 48 - 6 - quarter - down - 2 - - - - E - -1 - 3 - - 48 - 6 - quarter - down - 2 - - - - + + + diff --git a/tests/test_partial_measures.py b/tests/test_partial_measures.py index 1a14600d..45f4a2e0 100644 --- a/tests/test_partial_measures.py +++ b/tests/test_partial_measures.py @@ -1,77 +1,63 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- + +#%% """ -This module contains tests for partial measures. +This module contains tests for measures with non-integer IDs in musicxml. + +Such measure IDs can occur in irregular measures (i.e., pickups in the middle of the piece, i.e. variations pieces, measures with fermata or cadenza ornamentations. + +Fix: added 'name' property to measures to reflect non-integer measures. """ import unittest -import os import numpy as np -import partitura -from partitura import load_musicxml -from partitura import score - -import xml.etree.ElementTree as ET - -# TODO tmp, del later -import warnings -warnings.filterwarnings("ignore") - - - # TODO # import error, doesn't recognise path -# from tests import MUSICXML_PARTIAL_MEASURES_TESTFILES -tests_dir = os.path.dirname(os.path.abspath(__file__)) -tests = os.path.dirname(os.path.abspath(__file__)) -partial_measures_test_file = os.path.join(tests, 'data', 'musicxml', 'test_partial_measures.musicxml') -consecutive_partial_measures_test_file = os.path.join(tests, 'data', 'musicxml', 'test_partial_measures_consecutive.musicxml') - -# test_partial_measures.musicxml: Var. V from Sonata K331, 1. mov -spart = load_musicxml(partial_measures_test_file).parts[0] -snote_array = spart.note_array() -# print(type(spart)) # part -# print(type(snote_array)) # ndarray - -# print(dir(spart)) -# print([i for i in dir(spart) if 'measure' in i]) +from partitura.io import load_musicxml +from partitura.score import * -# print(type(spart.notes)) # list -# print(dir(spart.notes[0])) # list +from tests import MUSICXML_PARTIAL_MEASURES_TESTFILES -# print(type(spart.measures)) # list -# print(type(spart.measures[0])) # Measure -# print(dir(spart.measures[0])) # - -for measure in spart.measures[:10]: - # print(type(measure.number)) - print(measure.number) - -for note in spart.notes: - print(f"Note {note.id} starts in measure {spart.measure_number_map(note.start.t)}") # breaks - # print(f"Note {note.id} starts is contained in beats {spart.measure_map(note.start.t)}") # works - - - -# print(spart.measure_map) -# print(spart.measure_number_map) -# print(spart.measures) - - -# def create_measures_notes_dict(score_path, piece): - -# measures_notes_dict = {} -# musicxml_file = os.path.join(score_path, 'K%s-%s.musicxml' % (piece[:3], piece[-1])) -# root = ET.parse(musicxml_file) - -# for measure in root.findall("//measure"): -# measure_number = measure.get('number') -# notes = measure.findall('note') -# notes_id = [note.get('id') for note in notes] -# # measures can be organised within parts, or parts within measures. -# # handles the case where the same measures are split between different parts -# if measure_number in measures_notes_dict: -# measures_notes_dict[measure_number].extend(notes_id) -# else: -# measures_notes_dict[measure_number] = notes_id -# return measures_notes_dict +class TestPartialMeasures(unittest.TestCase): + """ + Test parsing of musicxml files with single partial/irregular measures + """ + def test_measure_number_name_single(self): + sc = load_musicxml(MUSICXML_PARTIAL_MEASURES_TESTFILES[0]) + spart = sc.parts[0] + spart_variants = make_score_variants(spart) + spart_unfolded = spart_variants[0].create_variant_part() + + unfolded_measure_numbers = [m.number for m in spart_unfolded.measures] + unfolded_measure_names = [m.name for m in spart_unfolded.measures] + + expected_unfolded_measure_numbers = [1,2,3,1,2,4,5,6,7] + expected_unfolded_measure_names = ['1','2','3','1','2','X1','4','X2','5'] + + self.assertTrue( + np.array_equal(unfolded_measure_numbers, expected_unfolded_measure_numbers) + ) + self.assertTrue( + np.array_equal(unfolded_measure_names, expected_unfolded_measure_names) + ) + + def test_measure_number_name_consecutive(self): + sc = load_musicxml(MUSICXML_PARTIAL_MEASURES_TESTFILES[1]) + spart = sc.parts[0] + + measure_numbers = [m.number for m in spart.measures] + measure_names = [m.name for m in spart.measures] + + expected_measure_numbers = [1,2,3,4,5,6,7] + expected_measure_names = ['1','2','3','X1','X2','X3','4'] + + self.assertTrue( + np.array_equal(measure_numbers, expected_measure_numbers) + ) + self.assertTrue( + np.array_equal(measure_names, expected_measure_names) + ) + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From 6042054405a9b119b0c4f415d03d034bf688618e Mon Sep 17 00:00:00 2001 From: Patricia Hu Date: Mon, 5 Jun 2023 12:20:46 +0200 Subject: [PATCH 064/122] added modified docstring for Measure object --- partitura/score.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/partitura/score.py b/partitura/score.py index 494c10dc..1f3ec0b9 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -2401,16 +2401,16 @@ class Measure(TimedObject): Parameters ---------- - counter : int - The running count independent of measure regularity/ volta endings, continuously counting up all measures in a musicxml score file (ignoring unfoldings) and always starting from one. - number : string, optional - The number of the measure in a given musicxml score file. Can be a non-number in case of volta endings, irregular measures (i.e., pickup measures in the middle of the piece). Defaults to None + number : int + The running count independent of measure regularity/ volta endings, continuously counting up all measures in a musicxml score file and always starting from one. + name : string, optional + The ID of the measure in a given musicxml score file. Can be a non-number in case of volta endings, irregular measures (i.e., pickup measures in the middle of the piece). Defaults to None Attributes ---------- - number : str + number : int See parameters - counter : int + name : str See parameters """ From 571cb6a28b25fc3cc776a689dc684a18be2008ee Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 5 Jun 2023 12:26:06 +0200 Subject: [PATCH 065/122] Removed torch from optional dependencies related to other discussion and PR. --- .github/workflows/partitura_unittests.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index d2b4388a..b105130e 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -28,7 +28,6 @@ jobs: - name: Instal Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 - pip install torch - name: Run Tests run: | pip install coverage From 323082240ab42581baf892d0badbeb0142602718 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 5 Jun 2023 13:04:42 +0200 Subject: [PATCH 066/122] Included documentation and typing, minor corrections, some note array testing, and lexordering. --- .../musicanalysis/note_array_to_score.py | 143 ++++++++++++++---- 1 file changed, 113 insertions(+), 30 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 4cfa6872..2148f546 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -1,4 +1,3 @@ -import partitura.score from partitura.score import ScoreLike, Part from partitura.utils import (estimate_symbolic_duration, estimate_clef_properties, key_name_to_fifths_mode, fifths_mode_to_key_name) import warnings @@ -10,7 +9,20 @@ import partitura.score as score -def create_divs_from_beats(note_array): +def create_divs_from_beats(note_array: np.ndarray): + """ + Append onset_div and duration_div fields to the note array. + + Parameters + ---------- + note_array: np.ndarray + The note array to which the divs fields will be added. Normally only beat onset and duration are provided. + + Returns + ------- + np.ndarray + The note array with the divs fields added. + """ duration_fractions = [Fraction(float(ix)).limit_denominator(256) for ix in note_array["duration_beat"]] onset_fractions = [Fraction(float(ix)).limit_denominator(256) for ix in note_array["onset_beat"]] divs = np.lcm.reduce( @@ -25,14 +37,40 @@ def create_divs_from_beats(note_array): def create_part( - ticks, - note_array, - key_sigs, - part_id=None, - part_name=None, - sanitize=True, - anacrusis_divs=0 + ticks: int, + note_array: np.ndarray, + key_sigs: list, + part_id: str = None, + part_name: str = None, + sanitize: bool = True, + anacrusis_divs: int = 0 ): + """ + Create a part from a note array and a list of key signatures. + + Parameters + ---------- + ticks: int + The number of ticks per quarter note for the part creation. + note_array: np.ndarray + The note array from which the part will be created. + key_sigs: list + A list of key signatures. Each key signature is a tuple of the form (onset, key_name, offset). + part_id: str + The id of the part. + part_name: str + sanitize: bool + If True, then measures, tied-notes and triplets will be sanitized. + anacrusis_divs: int + The number of divisions in the anacrusis. If 0, then there is no anacrusis measure. + + Returns + ------- + part : partitura.score.Part + The part created from the note array and key signatures. + """ + + warnings.warn("create_part", stacklevel=2) part = Part(part_id, part_name=part_name, ) @@ -112,12 +150,21 @@ def create_part( return part -def note_array_to_score(note_array: Union[np.ndarray, list], part_id="", divs: int = None, assign_note_ids: bool = True, - ensurelist: bool = False, estimate_key: bool = False, sanitize: bool = True, scorify=True) -> ScoreLike: +def note_array_to_score( + note_array: Union[np.ndarray, list], + name_id: str = "", + divs: int = None, + key_sigs: list = None, + part_name: str = "", + assign_note_ids: bool = True, + ensurelist: bool = False, + estimate_key: bool = False, + sanitize: bool = True, + return_part: bool = False) -> ScoreLike: """ - A generic function to transform an enriched note_array to part. + A generic function to transform an enriched note_array to part or Score. - The function can be used for many different occasions, i.e. load_score_midi, part_from_match, part_from_graph, etc. + The function can be used for many different occasions, i.e. part_from_graph, part from note_array, part from midi score import, etc. This function requires a note array that contains time signatures and key signatures(optional - can also estimate it automatically). Note array should contain the following fields: - onset_div or onset_beat @@ -131,14 +178,30 @@ def note_array_to_score(note_array: Union[np.ndarray, list], part_id="", divs: i Parameters ---------- - note_array : structure array or list of structured arrays + note_array : structure array or list of structured arrays. + A note array with the following fields: + - onset_div or onset_beat + - duration_div or duration_beat + - pitch + - ts_beats + - ts_beat_type + - key_mode(optional) + - key_fifths(optional) + - id(required but can also be empty) divs : int (optional) Necessary Provided divs for midi import. + key_sigs : list (optional) + List of key signatures. Each key signature is a list of [onset_div, key_name, end_div]. assign_note_ids : bool (optional) - Assign note_ids + Assign note_ids. ensurelist: bool (optional) - ensure that output part is a list + ensure that output part is a list. estimate_key: bool (optional) + estimate key from note_array. + sanitize: bool (optional) + sanitize the part by adding measures, tying notes, and finding tuplets. + return_part: bool (optional) + Return a Partitura score object instead of a part. Returns ------- @@ -147,30 +210,38 @@ def note_array_to_score(note_array: Union[np.ndarray, list], part_id="", divs: i """ if isinstance(note_array, list): parts = [ - note_array_to_score(note_array=x, part_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist, scorify=False) for + note_array_to_score(note_array=x, name_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist, return_part=True) for i, x in enumerate(note_array)] return score.Score(partlist=parts) if not isinstance(note_array, np.ndarray): raise TypeError("The note array does not have the correct format.") + if len(note_array) == 0: raise ValueError("The note array is empty.") + # Test Note array for negative durations + if "duration_div" in note_array.dtype.names: + assert np.all(note_array["duration_div"] >= 0), "Note array contains negative durations." + elif "duration_beat" in note_array.dtype.names: + assert np.all(note_array["duration_beat"] >= 0), "Note array contains negative durations." + + # Note id creation or re-assignment if "id" not in note_array.dtype.names: - note_ids = ["{}n{:4d}".format(part_id, i) for i in range(len(note_array))] + note_ids = ["{}n{:4d}".format(name_id, i) for i in range(len(note_array))] note_array = rfn.append_fields(note_array, "id", np.array(note_ids, dtype='= 0), "Negative divs found in note_array." + # Order Lexicographically + note_array = note_array[np.lexsort((note_array["onset_div"], note_array["pitch"]))] + + # Create the part part = create_part( ticks=divs, note_array=note_array, key_sigs=global_key_sigs, - part_id=part_id, - part_name=None, + part_id=name_id, + part_name=part_name, sanitize=sanitize, anacrusis_divs=anacrusis_divs ) - if scorify: - return partitura.score.Score(partlist=[part]) - else: + # Return Part or Score + if return_part: return part + else: + return score.Score(partlist=[part], id=name_id) + From a1d08dcddf68c4f6d8cab5682e05129b9d5fbdd8 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 5 Jun 2023 13:04:58 +0200 Subject: [PATCH 067/122] Updated tests with anacrusis and multiple ts tests. --- tests/test_note_array.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_note_array.py b/tests/test_note_array.py index a4fa59bf..68f9c141 100644 --- a/tests/test_note_array.py +++ b/tests/test_note_array.py @@ -13,7 +13,7 @@ from partitura.musicanalysis import note_array_to_score import numpy as np -from tests import NOTE_ARRAY_TESTFILES, KERN_TESTFILES +from tests import NOTE_ARRAY_TESTFILES, KERN_TESTFILES, METRICAL_POSITION_TESTFILES class TestNoteArray(unittest.TestCase): @@ -71,7 +71,7 @@ def test_use_musical_beats2(self): self.assertTrue(score[0]._use_musical_beat == False) self.assertFalse(np.array_equal(score[0].note_array(), note_array)) - def test_note_array_to_score(self): + def test_note_array_to_score_multiple_ts(self): score = load_musicxml(NOTE_ARRAY_TESTFILES[0]) note_array = score.note_array(include_time_signature=True) new_score = note_array_to_score(note_array) @@ -79,6 +79,14 @@ def test_note_array_to_score(self): self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"])) self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + def test_note_array_to_score_anacrusis(self): + score = load_musicxml(METRICAL_POSITION_TESTFILES[1]) + note_array = score.note_array(include_time_signature=True) + new_score = note_array_to_score(note_array) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"])) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + def test_notearray_ts_beats(self): part = load_musicxml(NOTE_ARRAY_TESTFILES[0])[0] note_array = note_array_from_part(part, include_time_signature=True) From 95aadb8e0902eb40b1f2f62c9edd1dab8ba04749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 5 Jun 2023 14:26:21 +0200 Subject: [PATCH 068/122] minor reformatting --- partitura/io/importmusicxml.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index 74d49c55..946ca973 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -62,6 +62,7 @@ OCTAVE_SHIFTS = {8: 1, 15: 2, 22: 3} + def validate_musicxml(xml, debug=False): """ Validate an XML file against an XSD. @@ -199,9 +200,7 @@ def load_musicxml( if zipfile.is_zipfile(filename): with zipfile.ZipFile(filename) as zipped_xml: contained_xml_name = zipped_xml.namelist()[-1] - xml = zipped_xml.open( - contained_xml_name - ) + xml = zipped_xml.open(contained_xml_name) if xml is None: xml = filename @@ -299,7 +298,7 @@ def load_musicxml( lyricist=lyricist, copyright=copyright, ) - + return scr @@ -332,7 +331,7 @@ def _parse_parts(document, part_dict): for mc, measure_el in enumerate(part_el.xpath("measure")): position, doc_order = _handle_measure( - measure_el, position, part, ongoing, doc_order, mc+1 + measure_el, position, part, ongoing, doc_order, mc + 1 ) # complete unfinished endings @@ -704,7 +703,7 @@ def make_measure(xml_measure, measure_counter): # measure.number = int(xml_measure.attrib['number']) # except: # LOGGER.warn('No number attribute found for measure') - + measure.number = measure_counter measure.name = get_value_from_attribute(xml_measure, "number", str) From 02bf8eca519be7375fd893670ec5d9bdc0642ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 5 Jun 2023 15:01:01 +0200 Subject: [PATCH 069/122] fix typo --- partitura/io/importmusicxml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index 946ca973..e75f885a 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -721,7 +721,7 @@ def _handle_attributes(e, position, part): fifths = get_value_from_tag(e, "key/fifths", int) mode = get_value_from_tag(e, "key/mode", str) - if fifths is not None or mode is not jNone: + if fifths is not None or mode is not None: part.add(score.KeySignature(fifths, mode), position) diat = get_value_from_tag(e, "transpose/diatonic", int) From 5d8f19ebfa60163d819be3be0ce5b05c7bad2c7c Mon Sep 17 00:00:00 2001 From: Patricia Hu Date: Mon, 5 Jun 2023 15:37:08 +0200 Subject: [PATCH 070/122] fixed sideeffects from partial measures fix --- partitura/score.py | 2 +- tests/test_xml.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/partitura/score.py b/partitura/score.py index 1f3ec0b9..86335a03 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -3348,7 +3348,7 @@ def add_measures(part): else: measure_end = existing_measure.start.t - part.add(Measure(number=mcounter), int(measure_start), int(measure_end)) + part.add(Measure(number=mcounter, name=str(mcounter)), int(measure_start), int(measure_end)) # if measure exists but was not at measure_start, # a filler measure is added with number mcounter diff --git a/tests/test_xml.py b/tests/test_xml.py index 8323fb54..2cca87bc 100755 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -163,7 +163,7 @@ def test_export_import_pprint(self): ts = score.TimeSignature(3, 4) page1 = score.Page(1) system1 = score.System(1) - measure1 = score.Measure(number=1) + measure1 = score.Measure(number=1, name='1') note1 = score.Note(step="A", octave=4, voice=1, staff=1) rest1 = score.Rest(voice=1, staff=1) note2 = score.Note(step="C", octave=5, alter=-1, voice=2, staff=1) @@ -196,6 +196,7 @@ def test_export_import_pprint(self): # test pretty printed strings for equality equal = pstring1 == pstring2 + print('equal:', equal) if not equal: show_diff(pstring1, pstring2) From cf4d64497a570551c3c554d860c8dc80c0c4c8f3 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 5 Jun 2023 16:09:50 +0200 Subject: [PATCH 071/122] integrated note_array_to_score to import midi but still errors, apply for review comments TOFIX --- partitura/io/importmidi.py | 259 +++++++++++------- .../musicanalysis/note_array_to_score.py | 7 +- 2 files changed, 158 insertions(+), 108 deletions(-) diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index 4ff966d2..a698d5f4 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -439,10 +439,10 @@ def load_score_midi( ) # pairs of (part, voice) for each note - part_voice_list = [ - [part, voice] + note_list = [ + (o, p, d, tr_ch[0], tr_ch[1], part, (voice if voice is not None else 0)) for tr_ch, (_, part, voice) in zip(tr_ch_keys, group_part_voice_keys) - for i in range(len(notes_by_track_ch[tr_ch])) + for (o, p, d) in notes_by_track_ch[tr_ch] ] # pitch spelling, voice estimation and key estimation are done on a @@ -450,116 +450,167 @@ def load_score_midi( # jointly, so we concatenate all notes # note_list = sorted(note for notes in # (notes_by_track_ch[key] for key in tr_ch_keys) for note in notes) - note_list = [ - note - for notes in (notes_by_track_ch[key] for key in tr_ch_keys) - for note in notes - ] + # note_list = [(o, p, d, key[0]) for key in tr_ch_keys for (o, p, d) in notes_by_track_ch[key]] note_array = np.array( note_list, - dtype=[("onset_div", int), ("pitch", int), ("duration_div", int)], + dtype=[("onset_div", int), ("pitch", int), ("duration_div", int), ("track", int), ("channel", int), + ("part", int), ("voice", int)], ) - warnings.warn("pitch spelling") - spelling_global = analysis.estimate_spelling(note_array) - - if estimate_voice_info: - warnings.warn("voice estimation", stacklevel=2) - # TODO: deal with zero duration notes in note_array. - # Zero duration notes are currently deleted - estimated_voices = analysis.estimate_voices(note_array) - assert len(part_voice_list) == len(estimated_voices) - for part_voice, voice_est in zip(part_voice_list, estimated_voices): - if part_voice[1] is None: - part_voice[1] = voice_est - - if estimate_key: - warnings.warn("key estimation", stacklevel=2) - _, mode, fifths = analysis.estimate_key(note_array) - key_sigs_by_track = {} - global_key_sigs = [(0, fifths_mode_to_key_name(fifths, mode))] - - if assign_note_ids: - note_ids = ["n{}".format(i) for i in range(len(note_array))] - else: - note_ids = [None for i in range(len(note_array))] - - time_sigs_by_part = defaultdict(set) - for tr, ts_list in time_sigs_by_track.items(): - for ts in ts_list: - for part in track_to_part_mapping[tr]: - time_sigs_by_part[part].add(ts) - for ts in global_time_sigs: - for part in set(part for _, part, _ in group_part_voice_keys): - time_sigs_by_part[part].add(ts) - - key_sigs_by_part = defaultdict(set) - for tr, ks_list in key_sigs_by_track.items(): - for ks in ks_list: - for part in track_to_part_mapping[tr]: - key_sigs_by_part[part].add(ks) - for ks in global_key_sigs: - for part in set(part for _, part, _ in group_part_voice_keys): - key_sigs_by_part[part].add(ks) - - # names_by_part = defaultdict(set) - # for tr_ch, pg_p_v in zip(tr_ch_keys, group_part_voice_keys): - # print(tr_ch, pg_p_v) - # for tr, name in track_names_by_track.items(): - # print(tr, track_to_part_mapping, name) - # for part in track_to_part_mapping[tr]: - # names_by_part[part] = name - - notes_by_part = defaultdict(list) - for (part, voice), note, spelling, note_id in zip( - part_voice_list, note_list, spelling_global, note_ids - ): - notes_by_part[part].append((note, voice, spelling, note_id)) - - partlist = [] - part_to_part_group = dict((p, pg) for pg, p, _ in group_part_voice_keys) - part_groups = {} - for part_nr, note_info in notes_by_part.items(): - notes, voices, spellings, note_ids = zip(*note_info) - part = create_part( - divs, - notes, - spellings, - voices, - note_ids, - sorted(time_sigs_by_part[part_nr]), - sorted(key_sigs_by_part[part_nr]), - part_id="P{}".format(part_nr + 1), - part_name=part_names.get(part_nr, None), - ) - # print(part.pretty()) - # if this part has an associated part_group number we create a PartGroup - # if necessary, and add the part to that. The newly created PartGroup is - # then added to the partlist. - pg_nr = part_to_part_group[part_nr] - if pg_nr is None: - partlist.append(part) - else: - if pg_nr not in part_groups: - part_groups[pg_nr] = score.PartGroup( - group_name=group_names.get(pg_nr, None) - ) - partlist.append(part_groups[pg_nr]) - part_groups[pg_nr].children.append(part) + # Add Time Signature to note_array assume 4/4 and overwrite if different + ts_beats = np.full(len(note_array), 4, dtype=int) + ts_beat_type = np.full(len(note_array), 4, dtype=int) + for track_nr, time_sigs in time_sigs_by_track.items(): + idx_of_track = note_array["part"] == track_nr + na_by_track = note_array[idx_of_track] + ts_by_track = np.array(time_sigs) + ts_by_track = np.hstack((ts_by_track, np.expand_dims(np.r_[np.diff(ts_by_track[:, 0]), np.max(note_array["onset_div"])+1], 0))) - # add tempos to first part - part = next(score.iter_parts(partlist)) - for t, qpm in global_tempos: - part.add(score.Tempo(qpm, unit="q"), t) + for t_start, num, denom, t_end in ts_by_track: + idx_of_ts = (na_by_track["onset_div"] >= t_start) & (na_by_track["onset_div"] < t_end) + ts_beats[idx_of_track][idx_of_ts] = num + ts_beat_type[idx_of_track][idx_of_ts] = denom - # TODO: Add info (composer, etc.) - scr = score.Score( - id=doc_name, - partlist=partlist, + note_array = np.lib.recfunctions.append_fields( + note_array, "ts_beats", ts_beats, usemask=False + ) + note_array = np.lib.recfunctions.append_fields( + note_array, "ts_beat_type", ts_beat_type, usemask=False ) - return scr + if part_voice_assign_mode == 0: + grouping = np.sort(np.unique(note_array["track"])[::-1]) + grouping_key = "track" + note_array["voice"] = note_array["channel"] + elif part_voice_assign_mode == 1: + grouping = np.sort(np.unique(note_array["channel"])) + grouping_key = "channel" + elif part_voice_assign_mode == 2: + grouping = np.sort(np.unique(note_array["part"])) + grouping_key = "part" + note_array["voice"] = note_array["track"] + elif part_voice_assign_mode == 3: + grouping = np.sort(np.unique(note_array["track"])) + grouping_key = "track" + note_array["voice"] = 0 + elif part_voice_assign_mode == 4: + note_array["part"] = 0 + grouping = [0] + grouping_key = "part" + elif part_voice_assign_mode == 5: + grouping = np.sort(np.unique(note_array[["track", "channel"]])) + grouping_key = ["track", "channel"] + note_array["voice"] = 0 + else: + raise ValueError("part_voice_assign_mode must be between 0 and 5") + + note_arrays = [note_array[note_array[grouping_key] == g] for g in grouping] + return analysis.note_array_to_score(note_arrays, divs=divs, estimate_key=estimate_key, name_id=doc_name, assign_note_ids=assign_note_ids) + + + # + # + # warnings.warn("pitch spelling") + # spelling_global = analysis.estimate_spelling(note_array) + # + # if estimate_voice_info: + # warnings.warn("voice estimation", stacklevel=2) + # # TODO: deal with zero duration notes in note_array. + # # Zero duration notes are currently deleted + # estimated_voices = analysis.estimate_voices(note_array) + # assert len(part_voice_list) == len(estimated_voices) + # for part_voice, voice_est in zip(part_voice_list, estimated_voices): + # if part_voice[1] is None: + # part_voice[1] = voice_est + # + # if estimate_key: + # warnings.warn("key estimation", stacklevel=2) + # _, mode, fifths = analysis.estimate_key(note_array) + # key_sigs_by_track = {} + # global_key_sigs = [(0, fifths_mode_to_key_name(fifths, mode))] + # + # if assign_note_ids: + # note_ids = ["n{}".format(i) for i in range(len(note_array))] + # else: + # note_ids = [None for i in range(len(note_array))] + # + # time_sigs_by_part = defaultdict(set) + # for tr, ts_list in time_sigs_by_track.items(): + # for ts in ts_list: + # for part in track_to_part_mapping[tr]: + # time_sigs_by_part[part].add(ts) + # for ts in global_time_sigs: + # for part in set(part for _, part, _ in group_part_voice_keys): + # time_sigs_by_part[part].add(ts) + # + # key_sigs_by_part = defaultdict(set) + # for tr, ks_list in key_sigs_by_track.items(): + # for ks in ks_list: + # for part in track_to_part_mapping[tr]: + # key_sigs_by_part[part].add(ks) + # for ks in global_key_sigs: + # for part in set(part for _, part, _ in group_part_voice_keys): + # key_sigs_by_part[part].add(ks) + # + # # names_by_part = defaultdict(set) + # # for tr_ch, pg_p_v in zip(tr_ch_keys, group_part_voice_keys): + # # print(tr_ch, pg_p_v) + # # for tr, name in track_names_by_track.items(): + # # print(tr, track_to_part_mapping, name) + # # for part in track_to_part_mapping[tr]: + # # names_by_part[part] = name + # + # notes_by_part = defaultdict(list) + # for (part, voice), note, spelling, note_id in zip( + # part_voice_list, note_list, spelling_global, note_ids + # ): + # notes_by_part[part].append((note, voice, spelling, note_id)) + # + # partlist = [] + # part_to_part_group = dict((p, pg) for pg, p, _ in group_part_voice_keys) + # part_groups = {} + # for part_nr, note_info in notes_by_part.items(): + # notes, voices, spellings, note_ids = zip(*note_info) + # part = create_part( + # divs, + # notes, + # spellings, + # voices, + # note_ids, + # sorted(time_sigs_by_part[part_nr]), + # sorted(key_sigs_by_part[part_nr]), + # part_id="P{}".format(part_nr + 1), + # part_name=part_names.get(part_nr, None), + # ) + # + # # print(part.pretty()) + # # if this part has an associated part_group number we create a PartGroup + # # if necessary, and add the part to that. The newly created PartGroup is + # # then added to the partlist. + # pg_nr = part_to_part_group[part_nr] + # if pg_nr is None: + # partlist.append(part) + # else: + # if pg_nr not in part_groups: + # part_groups[pg_nr] = score.PartGroup( + # group_name=group_names.get(pg_nr, None) + # ) + # partlist.append(part_groups[pg_nr]) + # part_groups[pg_nr].children.append(part) + # + # # add tempos to first part + # part = next(score.iter_parts(partlist)) + # for t, qpm in global_tempos: + # part.add(score.Tempo(qpm, unit="q"), t) + # + # # TODO: Add info (composer, etc.) + # scr = score.Score( + # id=doc_name, + # partlist=partlist, + # ) + # + # return scr def make_track_to_part_mapping(tr_ch_keys, group_part_voice_keys): diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 2148f546..049c3c2b 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -157,7 +157,6 @@ def note_array_to_score( key_sigs: list = None, part_name: str = "", assign_note_ids: bool = True, - ensurelist: bool = False, estimate_key: bool = False, sanitize: bool = True, return_part: bool = False) -> ScoreLike: @@ -194,8 +193,6 @@ def note_array_to_score( List of key signatures. Each key signature is a list of [onset_div, key_name, end_div]. assign_note_ids : bool (optional) Assign note_ids. - ensurelist: bool (optional) - ensure that output part is a list. estimate_key: bool (optional) estimate key from note_array. sanitize: bool (optional) @@ -210,7 +207,9 @@ def note_array_to_score( """ if isinstance(note_array, list): parts = [ - note_array_to_score(note_array=x, name_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist, return_part=True) for + note_array_to_score(note_array=x, name_id=str(i), assign_note_ids=assign_note_ids, + return_part=True, divs=divs, estimate_key=estimate_key, sanitize=sanitize, + part_name=name_id+"_P"+str(i)) for i, x in enumerate(note_array)] return score.Score(partlist=parts) From 3482e867e5551e3ace975f6b795fe15497ccd537 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 5 Jun 2023 16:32:18 +0200 Subject: [PATCH 072/122] small changes to address comments. --- .../musicanalysis/note_array_to_score.py | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 049c3c2b..587168fd 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -54,14 +54,15 @@ def create_part( The number of ticks per quarter note for the part creation. note_array: np.ndarray The note array from which the part will be created. - key_sigs: list + key_sigs: list of tuples A list of key signatures. Each key signature is a tuple of the form (onset, key_name, offset). - part_id: str + part_id: str (default: None) The id of the part. - part_name: str - sanitize: bool + part_name: str (default: None) + The name of the part. + sanitize: bool (default: True) If True, then measures, tied-notes and triplets will be sanitized. - anacrusis_divs: int + anacrusis_divs: int (default: 0) The number of divisions in the anacrusis. If 0, then there is no anacrusis measure. Returns @@ -188,7 +189,7 @@ def note_array_to_score( - key_fifths(optional) - id(required but can also be empty) divs : int (optional) - Necessary Provided divs for midi import. + Divs is optional when note_array contains onset_beat and duration_beat. Otherwise it is required. key_sigs : list (optional) List of key signatures. Each key signature is a list of [onset_div, key_name, end_div]. assign_note_ids : bool (optional) @@ -202,8 +203,8 @@ def note_array_to_score( Returns ------- - part : list or Part or PartGroup - Maybe should return score + part : Score or Part + A part or score object. """ if isinstance(note_array, list): parts = [ @@ -235,7 +236,7 @@ def note_array_to_score( dtypes = note_array.dtype.names # check if note array contains time signatures - if not "ts_beats" in dtypes and key_sigs is None: + if not "ts_beats" in dtypes: raise AttributeError("The note array does not contain a time signature.") anacrusis_mask = np.zeros(len(note_array), dtype=bool) @@ -303,14 +304,15 @@ def note_array_to_score( warnings.warn("key estimation", stacklevel=2) k_name = analysis.estimate_key(note_array) global_key_sigs = [[0, k_name]] - else: - if key_sigs is None and "ts_beats" in dtypes: - global_key_sigs = [[0, fifths_mode_to_key_name(note_array[0]["ks_fifths"], note_array[0]["ks_mode"])]] - for n in note_array: - if n["ts_beats"] != global_key_sigs[-1][1] or n["ts_beat_type"] != global_key_sigs[-1][2]: - global_key_sigs.append([n["onset_div"], fifths_mode_to_key_name(n["ks_fifths"], n["ks_mode"])]) - else: - global_key_sigs = key_sigs + + if key_sigs is not None: + global_key_sigs = key_sigs + elif "ks_fifths" in dtypes or "ks_mode" in dtypes: + global_key_sigs = [[0, fifths_mode_to_key_name(note_array[0]["ks_fifths"], note_array[0]["ks_mode"])]] + for n in note_array: + if n["ts_beats"] != global_key_sigs[-1][1] or n["ts_beat_type"] != global_key_sigs[-1][2]: + global_key_sigs.append([n["onset_div"], fifths_mode_to_key_name(n["ks_fifths"], n["ks_mode"])]) + global_key_sigs = np.array(global_key_sigs) # for convenience, we add the end times for each time signature ks_end_times = np.r_[global_key_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] From 09c3a067e84b83bb5dc9a9fb2fc55958e658b3c0 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Mon, 5 Jun 2023 17:36:14 +0200 Subject: [PATCH 073/122] added wrapper and tests. Still having problems due to partitura midi export --- partitura/utils/music.py | 22 ++++++++++++++++++ tests/__init__.py | 9 +++++++ .../data/midi/mozart_k265_var1_quantized.mid | Bin 0 -> 1482 bytes tests/data/midi/test_anacrusis.mid | Bin 0 -> 222 bytes tests/test_utils.py | 15 +++++++++++- 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 tests/data/midi/mozart_k265_var1_quantized.mid create mode 100644 tests/data/midi/test_anacrusis.mid diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 24d3ba62..50cc0f93 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -13,6 +13,16 @@ from scipy.sparse import csc_matrix from typing import Union, Callable, Optional, TYPE_CHECKING from partitura.utils.generic import find_nearest, search, iter_current_next +import partitura +from tempfile import TemporaryDirectory +import os + +try: + import miditok + import miditoolkit +except ImportError: + miditok = None + miditoolkit = None from partitura.utils.misc import deprecated_alias @@ -3441,6 +3451,18 @@ def slice_ppart_by_time( return ppart_slice +def tokenize(score_data : ScoreLike, tokenizer : miditok.midi_tokenizer.MidiTokenizer): + if miditok is None: + raise ImportError("Miditok must be installed for this function to work") + with TemporaryDirectory() as tmpdir: + temp_midi_path = os.path.join(tmpdir, "temp_midi.mid") + partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, part_voice_assign_mode = 4 ) + midi = miditoolkit.MidiFile(temp_midi_path) + tokens = tokenizer(midi) + return tokens + + + if __name__ == "__main__": import doctest diff --git a/tests/__init__.py b/tests/__init__.py index 2092df1c..b44c78ba 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -211,3 +211,12 @@ ] PNG_TESTFILES = glob.glob(os.path.join(PNG_PATH, "*.png")) + +TOKENIZER_TESTFILES = [ + {"midi" : os.path.join(DATA_PATH, "midi", "test_anacrusis.mid"), + "score" : os.path.join(DATA_PATH, "musicxml", "test_anacrusis.xml"), + }, + {"midi" : os.path.join(DATA_PATH, "midi", "mozart_k265_var1_quantized.mid"), + "score" : os.path.join(DATA_PATH, "musicxml", "mozart_k265_var1.musicxml"), + }, +] diff --git a/tests/data/midi/mozart_k265_var1_quantized.mid b/tests/data/midi/mozart_k265_var1_quantized.mid new file mode 100644 index 0000000000000000000000000000000000000000..63028d080722a87f199150cc1b0339740fccbdf3 GIT binary patch literal 1482 zcmeHHyH4Ct5IuJxxe?MTMH<|NDkY>q7Fx40l^3(d$6|vvic&-%B~hfHNwcC{ER;6C zkU!uP_>}yKbG_rNMtNxIXsGU-JI6D}bFQYCd^`v67!E!6vzUDS46MIEv-XIe-ha73 zGhOfXHlLt*(+jkwTJt}iS`qhLofj^;~z@Ji1;q8rbD$Xi6tv{tq|IbuT$yo_kP>NMdU2I2|NWUAD31SZ&r*oxgfyIaZt2(eCQ0`fE8>o7Jq9EmNzNy!sGK9dkB@%fpa! zL=W#&=W2bhb6x#TU9$~c7w+1u3tbb7U9b~Iy@MzTM}jB~N4i7@0Uaz+LbT}wNKw0e z%0B!sTnqymPzLZ?q;`9hJ^0;_+eH+IBVHEi+I4-poL2dR+2uM79g15G-=4;;hL-^h m6$}@kZ67j8(34OS_>_ow^*Y5f#RJycF*XN??&YsrGkyUgU|L`R literal 0 HcmV?d00001 diff --git a/tests/data/midi/test_anacrusis.mid b/tests/data/midi/test_anacrusis.mid new file mode 100644 index 0000000000000000000000000000000000000000..55c830ca8f05762584a7fb0b8ea0d8d1572f75a2 GIT binary patch literal 222 zcmeYb$w*;fU|?flWME``;2Tnu4dk6*_|ME5keQg5&+tEjg^@{ugW-Q96HwxRAT#?y z1%?fk3=9Vt7&fq{FmTy3L^CkN0;Log85kzm1T?$bFh~M*v4C_ Date: Mon, 5 Jun 2023 18:22:42 +0200 Subject: [PATCH 074/122] minor update of docstring. --- partitura/musicanalysis/note_array_to_score.py | 1 + 1 file changed, 1 insertion(+) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 587168fd..4ac8e6a9 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -13,6 +13,7 @@ def create_divs_from_beats(note_array: np.ndarray): """ Append onset_div and duration_div fields to the note array. + This function may result in an error if time signature changes that affect the ratio of beat/div are present. Parameters ---------- note_array: np.ndarray From a45e42ad3a2719065c5e7c56eb096e62bbf882f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 5 Jun 2023 18:47:16 +0200 Subject: [PATCH 075/122] fix issue 257 --- partitura/io/importmusic21.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py index 1176bc9e..5e20e9a1 100644 --- a/partitura/io/importmusic21.py +++ b/partitura/io/importmusic21.py @@ -2,15 +2,19 @@ import numpy as np from fractions import Fraction -from partitura.utils import generic try: import music21 as m21 + from music21.stream import Score as M21Score except ImportError: + m21 = None + class M21Score(object): + pass + -def load_music21(m21_score: m21.stream.Score) -> pt.score.Score: +def load_music21(m21_score: M21Score) -> pt.score.Score: """ Loads a music21 score object and returns a Partitura Score object. @@ -107,7 +111,7 @@ def fill_part_notes(self, m21_part, pt_part, part_idx): if pitch.accidental is not None else None, # id="{}_{}".format(generic_note.id, i_pitch), - id = generic_note.id, + id=generic_note.id, voice=self.find_voice(generic_note), staff=part_idx + 1, symbolic_duration=generic_note.duration.type, @@ -121,7 +125,7 @@ def fill_part_notes(self, m21_part, pt_part, part_idx): if pitch.accidental is not None else None, # id="{}_{}".format(generic_note.id, i_pitch), - id = generic_note.id, + id=generic_note.id, voice=self.find_voice(generic_note), staff=part_idx + 1, symbolic_duration=generic_note.duration.type, @@ -146,32 +150,29 @@ def fill_part_ks(self, m21_part, pt_part, part_idx): new_key_signature = pt.score.KeySignature(ks.sharps, None) position = int(ks.getOffsetInHierarchy(self.m21_score) * self.ppq) pt_part.add(new_key_signature, position) - + def tie_notes(self, m21_score, pt_part_list): """Fills the part with ties""" # create a dict of id : note, to speed up search all_notes = [ - note - for part in pt_part_list - for note in part.iter_all(cls=pt.score.Note) + note for part in pt_part_list for note in part.iter_all(cls=pt.score.Note) ] all_notes_dict = {note.id: note for note in all_notes} for m21_note in m21_score.recurse().getElementsByClass(m21.note.Note): if m21_note.tie is not None: if m21_note.tie.type == "start": start_id = m21_note.id - end_id = m21_note.next('Note').id + end_id = m21_note.next("Note").id elif m21_note.tie.type == "stop": - pass # music21 don't require the stop to be set + pass # music21 don't require the stop to be set elif m21_note.tie.type == "continue": start_id = m21_note.id - end_id = m21_note.next('Note').id + end_id = m21_note.next("Note").id # set tie prev and tie next in partitura note objects all_notes_dict[start_id].tie_next = all_notes_dict[end_id] all_notes_dict[end_id].tie_prev = all_notes_dict[start_id] - def fill_part_clefs(self, m21_part, pt_part, part_idx): """Fills the part with clefs""" for m21_clef in m21_part.recurse().getElementsByClass(m21.clef.Clef): From 2d935657d1ccf0bb49cd5654d119c556ef946282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Cancino-Chac=C3=B3n?= Date: Mon, 5 Jun 2023 18:50:40 +0200 Subject: [PATCH 076/122] minor formatting --- partitura/io/importmusic21.py | 1 - 1 file changed, 1 deletion(-) diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py index 5e20e9a1..0e977a12 100644 --- a/partitura/io/importmusic21.py +++ b/partitura/io/importmusic21.py @@ -2,7 +2,6 @@ import numpy as np from fractions import Fraction - try: import music21 as m21 from music21.stream import Score as M21Score From e89d893a8e25b1c683a8bd4313842d2375841e02 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Tue, 6 Jun 2023 12:31:25 +0200 Subject: [PATCH 077/122] revert changes for import midi. --- partitura/io/importmidi.py | 260 +++++++++++++++---------------------- 1 file changed, 105 insertions(+), 155 deletions(-) diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index a698d5f4..d560f52a 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -428,6 +428,7 @@ def load_score_midi( if len(ch_notes) > 0: notes_by_track_ch[(track_nr, ch)] = ch_notes + tr_ch_keys = sorted(notes_by_track_ch.keys()) group_part_voice_keys, part_names, group_names = assign_group_part_voice( part_voice_assign_mode, tr_ch_keys, track_names_by_track @@ -439,10 +440,10 @@ def load_score_midi( ) # pairs of (part, voice) for each note - note_list = [ - (o, p, d, tr_ch[0], tr_ch[1], part, (voice if voice is not None else 0)) + part_voice_list = [ + [part, voice] for tr_ch, (_, part, voice) in zip(tr_ch_keys, group_part_voice_keys) - for (o, p, d) in notes_by_track_ch[tr_ch] + for i in range(len(notes_by_track_ch[tr_ch])) ] # pitch spelling, voice estimation and key estimation are done on a @@ -450,167 +451,116 @@ def load_score_midi( # jointly, so we concatenate all notes # note_list = sorted(note for notes in # (notes_by_track_ch[key] for key in tr_ch_keys) for note in notes) - # note_list = [(o, p, d, key[0]) for key in tr_ch_keys for (o, p, d) in notes_by_track_ch[key]] + note_list = [ + note + for notes in (notes_by_track_ch[key] for key in tr_ch_keys) + for note in notes + ] note_array = np.array( note_list, - dtype=[("onset_div", int), ("pitch", int), ("duration_div", int), ("track", int), ("channel", int), - ("part", int), ("voice", int)], + dtype=[("onset_div", int), ("pitch", int), ("duration_div", int)], ) + warnings.warn("pitch spelling") + spelling_global = analysis.estimate_spelling(note_array) + + if estimate_voice_info: + warnings.warn("voice estimation", stacklevel=2) + # TODO: deal with zero duration notes in note_array. + # Zero duration notes are currently deleted + estimated_voices = analysis.estimate_voices(note_array) + assert len(part_voice_list) == len(estimated_voices) + for part_voice, voice_est in zip(part_voice_list, estimated_voices): + if part_voice[1] is None: + part_voice[1] = voice_est + + if estimate_key: + warnings.warn("key estimation", stacklevel=2) + _, mode, fifths = analysis.estimate_key(note_array) + key_sigs_by_track = {} + global_key_sigs = [(0, fifths_mode_to_key_name(fifths, mode))] + + if assign_note_ids: + note_ids = ["n{}".format(i) for i in range(len(note_array))] + else: + note_ids = [None for i in range(len(note_array))] + + time_sigs_by_part = defaultdict(set) + for tr, ts_list in time_sigs_by_track.items(): + for ts in ts_list: + for part in track_to_part_mapping[tr]: + time_sigs_by_part[part].add(ts) + for ts in global_time_sigs: + for part in set(part for _, part, _ in group_part_voice_keys): + time_sigs_by_part[part].add(ts) + + key_sigs_by_part = defaultdict(set) + for tr, ks_list in key_sigs_by_track.items(): + for ks in ks_list: + for part in track_to_part_mapping[tr]: + key_sigs_by_part[part].add(ks) + for ks in global_key_sigs: + for part in set(part for _, part, _ in group_part_voice_keys): + key_sigs_by_part[part].add(ks) + + # names_by_part = defaultdict(set) + # for tr_ch, pg_p_v in zip(tr_ch_keys, group_part_voice_keys): + # print(tr_ch, pg_p_v) + # for tr, name in track_names_by_track.items(): + # print(tr, track_to_part_mapping, name) + # for part in track_to_part_mapping[tr]: + # names_by_part[part] = name + + notes_by_part = defaultdict(list) + for (part, voice), note, spelling, note_id in zip( + part_voice_list, note_list, spelling_global, note_ids + ): + notes_by_part[part].append((note, voice, spelling, note_id)) + + partlist = [] + part_to_part_group = dict((p, pg) for pg, p, _ in group_part_voice_keys) + part_groups = {} + for part_nr, note_info in notes_by_part.items(): + notes, voices, spellings, note_ids = zip(*note_info) + part = create_part( + divs, + notes, + spellings, + voices, + note_ids, + sorted(time_sigs_by_part[part_nr]), + sorted(key_sigs_by_part[part_nr]), + part_id="P{}".format(part_nr + 1), + part_name=part_names.get(part_nr, None), + ) - # Add Time Signature to note_array assume 4/4 and overwrite if different - ts_beats = np.full(len(note_array), 4, dtype=int) - ts_beat_type = np.full(len(note_array), 4, dtype=int) - for track_nr, time_sigs in time_sigs_by_track.items(): - idx_of_track = note_array["part"] == track_nr - na_by_track = note_array[idx_of_track] - ts_by_track = np.array(time_sigs) - ts_by_track = np.hstack((ts_by_track, np.expand_dims(np.r_[np.diff(ts_by_track[:, 0]), np.max(note_array["onset_div"])+1], 0))) + # print(part.pretty()) + # if this part has an associated part_group number we create a PartGroup + # if necessary, and add the part to that. The newly created PartGroup is + # then added to the partlist. + pg_nr = part_to_part_group[part_nr] + if pg_nr is None: + partlist.append(part) + else: + if pg_nr not in part_groups: + part_groups[pg_nr] = score.PartGroup( + group_name=group_names.get(pg_nr, None) + ) + partlist.append(part_groups[pg_nr]) + part_groups[pg_nr].children.append(part) - for t_start, num, denom, t_end in ts_by_track: - idx_of_ts = (na_by_track["onset_div"] >= t_start) & (na_by_track["onset_div"] < t_end) - ts_beats[idx_of_track][idx_of_ts] = num - ts_beat_type[idx_of_track][idx_of_ts] = denom + # add tempos to first part + part = next(score.iter_parts(partlist)) + for t, qpm in global_tempos: + part.add(score.Tempo(qpm, unit="q"), t) - note_array = np.lib.recfunctions.append_fields( - note_array, "ts_beats", ts_beats, usemask=False - ) - note_array = np.lib.recfunctions.append_fields( - note_array, "ts_beat_type", ts_beat_type, usemask=False + # TODO: Add info (composer, etc.) + scr = score.Score( + id=doc_name, + partlist=partlist, ) - if part_voice_assign_mode == 0: - grouping = np.sort(np.unique(note_array["track"])[::-1]) - grouping_key = "track" - note_array["voice"] = note_array["channel"] - elif part_voice_assign_mode == 1: - grouping = np.sort(np.unique(note_array["channel"])) - grouping_key = "channel" - elif part_voice_assign_mode == 2: - grouping = np.sort(np.unique(note_array["part"])) - grouping_key = "part" - note_array["voice"] = note_array["track"] - elif part_voice_assign_mode == 3: - grouping = np.sort(np.unique(note_array["track"])) - grouping_key = "track" - note_array["voice"] = 0 - elif part_voice_assign_mode == 4: - note_array["part"] = 0 - grouping = [0] - grouping_key = "part" - elif part_voice_assign_mode == 5: - grouping = np.sort(np.unique(note_array[["track", "channel"]])) - grouping_key = ["track", "channel"] - note_array["voice"] = 0 - else: - raise ValueError("part_voice_assign_mode must be between 0 and 5") - - note_arrays = [note_array[note_array[grouping_key] == g] for g in grouping] - return analysis.note_array_to_score(note_arrays, divs=divs, estimate_key=estimate_key, name_id=doc_name, assign_note_ids=assign_note_ids) - - - # - # - # warnings.warn("pitch spelling") - # spelling_global = analysis.estimate_spelling(note_array) - # - # if estimate_voice_info: - # warnings.warn("voice estimation", stacklevel=2) - # # TODO: deal with zero duration notes in note_array. - # # Zero duration notes are currently deleted - # estimated_voices = analysis.estimate_voices(note_array) - # assert len(part_voice_list) == len(estimated_voices) - # for part_voice, voice_est in zip(part_voice_list, estimated_voices): - # if part_voice[1] is None: - # part_voice[1] = voice_est - # - # if estimate_key: - # warnings.warn("key estimation", stacklevel=2) - # _, mode, fifths = analysis.estimate_key(note_array) - # key_sigs_by_track = {} - # global_key_sigs = [(0, fifths_mode_to_key_name(fifths, mode))] - # - # if assign_note_ids: - # note_ids = ["n{}".format(i) for i in range(len(note_array))] - # else: - # note_ids = [None for i in range(len(note_array))] - # - # time_sigs_by_part = defaultdict(set) - # for tr, ts_list in time_sigs_by_track.items(): - # for ts in ts_list: - # for part in track_to_part_mapping[tr]: - # time_sigs_by_part[part].add(ts) - # for ts in global_time_sigs: - # for part in set(part for _, part, _ in group_part_voice_keys): - # time_sigs_by_part[part].add(ts) - # - # key_sigs_by_part = defaultdict(set) - # for tr, ks_list in key_sigs_by_track.items(): - # for ks in ks_list: - # for part in track_to_part_mapping[tr]: - # key_sigs_by_part[part].add(ks) - # for ks in global_key_sigs: - # for part in set(part for _, part, _ in group_part_voice_keys): - # key_sigs_by_part[part].add(ks) - # - # # names_by_part = defaultdict(set) - # # for tr_ch, pg_p_v in zip(tr_ch_keys, group_part_voice_keys): - # # print(tr_ch, pg_p_v) - # # for tr, name in track_names_by_track.items(): - # # print(tr, track_to_part_mapping, name) - # # for part in track_to_part_mapping[tr]: - # # names_by_part[part] = name - # - # notes_by_part = defaultdict(list) - # for (part, voice), note, spelling, note_id in zip( - # part_voice_list, note_list, spelling_global, note_ids - # ): - # notes_by_part[part].append((note, voice, spelling, note_id)) - # - # partlist = [] - # part_to_part_group = dict((p, pg) for pg, p, _ in group_part_voice_keys) - # part_groups = {} - # for part_nr, note_info in notes_by_part.items(): - # notes, voices, spellings, note_ids = zip(*note_info) - # part = create_part( - # divs, - # notes, - # spellings, - # voices, - # note_ids, - # sorted(time_sigs_by_part[part_nr]), - # sorted(key_sigs_by_part[part_nr]), - # part_id="P{}".format(part_nr + 1), - # part_name=part_names.get(part_nr, None), - # ) - # - # # print(part.pretty()) - # # if this part has an associated part_group number we create a PartGroup - # # if necessary, and add the part to that. The newly created PartGroup is - # # then added to the partlist. - # pg_nr = part_to_part_group[part_nr] - # if pg_nr is None: - # partlist.append(part) - # else: - # if pg_nr not in part_groups: - # part_groups[pg_nr] = score.PartGroup( - # group_name=group_names.get(pg_nr, None) - # ) - # partlist.append(part_groups[pg_nr]) - # part_groups[pg_nr].children.append(part) - # - # # add tempos to first part - # part = next(score.iter_parts(partlist)) - # for t, qpm in global_tempos: - # part.add(score.Tempo(qpm, unit="q"), t) - # - # # TODO: Add info (composer, etc.) - # scr = score.Score( - # id=doc_name, - # partlist=partlist, - # ) - # - # return scr + return scr def make_track_to_part_mapping(tr_ch_keys, group_part_voice_keys): From 437e4b2d69cd43e821d56191e8c6cce821c3ecb5 Mon Sep 17 00:00:00 2001 From: Huan Zhang <31784445+Kristin33@users.noreply.github.com> Date: Tue, 6 Jun 2023 14:57:41 +0300 Subject: [PATCH 078/122] monotonize_times update (from carlos) --- partitura/musicanalysis/performance_codec.py | 39 +------------ partitura/utils/generic.py | 59 ++++++++++++++++---- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 7bf984fa..7c0a5a6c 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -26,7 +26,7 @@ def __init__(self): from partitura.performance import PerformedPart, PerformanceLike from partitura.musicanalysis import note_features from partitura.utils.misc import deprecated_alias -from partitura.utils.generic import interp1d +from partitura.utils.generic import interp1d, monotonize_times from partitura.utils.music import ensure_notearray from scipy.misc import derivative @@ -880,43 +880,6 @@ def get_unique_onset_idxs( return unique_onset_idxs -def monotonize_times(s, deltas=None): - """Interpolate linearly over as many points in `s` as necessary to - obtain a monotonic sequence. The minimum and maximum of `s` are - prepended and appended, respectively, to ensure monotonicity at - the bounds of `s`. - Parameters - ---------- - s : ndarray - a sequence of numbers - deltas : - position of the numbers (onset times) - Returns - ------- - ndarray - a monotonic sequence that has been linearly interpolated using a subset of s - """ - eps = np.finfo(float).eps - - _s = np.r_[np.min(s) - eps, s, np.max(s) + eps] - if deltas is not None: - _deltas = np.r_[np.min(deltas) - eps, deltas, np.max(deltas) + eps] - else: - _deltas = None - idx = np.arange(_s.shape[0]) - - # detect the position with value decrease and remove them from the interpolation values - s_mono = np.maximum.accumulate(s) - mask = np.r_[False, True, (np.diff(s_mono) != 0), False] - - s_mono = interp1d(idx[mask], _s[mask])(idx[1:-1]) - try: - assert((np.diff(s_mono) >= 0).all()) - except: - warnings.warn("Monotonize_time produce non-monotonic sequence!") - return s_mono, _deltas[1:-1] - - def notewise_to_onsetwise(notewise_inputs, unique_onset_idxs): """Agregate basis functions per onset""" if isinstance(notewise_inputs, np.ndarray): diff --git a/partitura/utils/generic.py b/partitura/utils/generic.py index d4130a5f..844d218d 100644 --- a/partitura/utils/generic.py +++ b/partitura/utils/generic.py @@ -6,7 +6,7 @@ import warnings from collections import defaultdict -from typing import Union, Callable, Optional +from typing import Union, Callable, Optional, Tuple from textwrap import dedent import numpy as np @@ -239,15 +239,15 @@ def replace_refs(self, o_map): if o_el in o_map: o_list_new.append(o_map[o_el]) else: - warnings.warn( - dedent( - """reference not found in - o_map: {} start={} end={}, substituting None - """.format( - o_el, o_el.start, o_el.end - ) - ) - ) + # warnings.warn( + # dedent( + # """reference not found in + # o_map: {} start={} end={}, substituting None + # """.format( + # o_el, o_el.start, o_el.end + # ) + # ) + # ) o_list_new.append(None) setattr(self, attr, o_list_new) @@ -615,6 +615,45 @@ def typed_interp( # return None +def monotonize_times( + s: np.ndarray, + x: Optional[np.ndarray] = None, +) -> Tuple[np.ndarray, np.ndarray]: + """ + Interpolate linearly over as many points in `s` as necessary to + obtain a monotonic sequence. The minimum and maximum of `s` are + prepended and appended, respectively, to ensure monotonicity at + the bounds of `s`. + Parameters + ---------- + s : np.ndarray + a sequence of numbers s(x) which we want to monotonize + x : np.ndarray or None + The input variable of sequence s(x). + Returns + ------- + s_mono: np.ndarray + a monotonic sequence that has been linearly interpolated using a subset of s + x_mono: np.ndarray + The input of the monotonic sequence. + """ + eps = np.finfo(float).eps + + _s = np.r_[np.min(s) - eps, s, np.max(s) + eps] + if x is not None: + _x = np.r_[np.min(x) - eps, x, np.max(x) + eps] + else: + _x = np.r_[-eps, np.arange(len(s)), len(s) - 1 + eps] + idx = np.arange(_s.shape[0]) + + s_mono = np.maximum.accumulate(s) + mask = np.r_[False, True, (np.diff(s_mono) != 0), False] + + x_mono = _x[1:-1] + s_mono = interp1d(idx[mask], _s[mask])(x_mono) + + return s_mono, x_mono + if __name__ == "__main__": import doctest From f6999a0a7090dbe7aaf184c31d86418a8099a0a9 Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 15:34:03 +0200 Subject: [PATCH 079/122] more general note array to part (untested) --- .../musicanalysis/note_array_to_score.py | 404 ++++++++++++------ 1 file changed, 268 insertions(+), 136 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 2148f546..c5dfcb82 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -12,16 +12,22 @@ def create_divs_from_beats(note_array: np.ndarray): """ Append onset_div and duration_div fields to the note array. + Assumes beats are in uniform units across the whole array + (no time signature change that modifies beat unit, e.g., 4/4 to 6/8). Parameters ---------- note_array: np.ndarray - The note array to which the divs fields will be added. Normally only beat onset and duration are provided. + The note array to which the divs fields will be added. + Normally only beat onset and duration are provided. Returns ------- - np.ndarray + note_array: np.ndarray The note array with the divs fields added. + divs: int + the divs per beat + """ duration_fractions = [Fraction(float(ix)).limit_denominator(256) for ix in note_array["duration_beat"]] onset_fractions = [Fraction(float(ix)).limit_denominator(256) for ix in note_array["onset_beat"]] @@ -33,17 +39,43 @@ def create_divs_from_beats(note_array: np.ndarray): onset_divs = list(map(lambda x: x - min_onset_div, onset_divs)) duration_divs = list(map(lambda r: int(divs * r.numerator / r.denominator), duration_fractions)) na_divs = np.array(list(zip(onset_divs, duration_divs)), dtype=[("onset_div", int), ("duration_div", int)]) - return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False) + return rfn.merge_arrays((note_array, na_divs), flatten=True, usemask=False), divs + + +def create_beats_from_divs(note_array: np.ndarray, divs: int): + """ + Append onset_beats and duration_beasts fields to the note array. + Returns beats in quarters. + Parameters + ---------- + note_array: np.ndarray + The note array to which the divs fields will be added. + Normally only beat onset and duration are provided. + divs: int + Divs/ticks per quarter note. + + Returns + ------- + note_array: np.ndarray + The note array with the divs fields added. + + """ + onset_beats = list(note_array["onset_div"]/divs) + duration_beats = list(note_array["duration_div"]/divs) + na_beats = np.array(list(zip(onset_beats, duration_beats)), dtype=[("onset_beat", int), ("duration_beat", int)]) + return rfn.merge_arrays((note_array, na_beats), flatten=True, usemask=False) def create_part( ticks: int, note_array: np.ndarray, - key_sigs: list, + key_sigs: list = [], + time_sigs: list = [], part_id: str = None, part_name: str = None, sanitize: bool = True, - anacrusis_divs: int = 0 + anacrusis_divs: int = 0, + barebones: bool = False, ): """ Create a part from a note array and a list of key signatures. @@ -54,15 +86,20 @@ def create_part( The number of ticks per quarter note for the part creation. note_array: np.ndarray The note array from which the part will be created. - key_sigs: list + key_sigs: list (optional) A list of key signatures. Each key signature is a tuple of the form (onset, key_name, offset). - part_id: str + time_sigs: list (optional) + A list of time signatures. Each time signature is a tuple of the form (onset, ts_num, ts_den, offset). + part_id: str (optional) The id of the part. - part_name: str - sanitize: bool + part_name: str (optional) + The name of the part + sanitize: bool (optional) If True, then measures, tied-notes and triplets will be sanitized. - anacrusis_divs: int + anacrusis_divs: int (optional) The number of divisions in the anacrusis. If 0, then there is no anacrusis measure. + barebones: bool (optional) + Returns a part with only notes, no measures Returns ------- @@ -80,13 +117,29 @@ def create_part( staff=1, **estimate_clef_properties(note_array["pitch"]) ) part.add(clef, 0) - for t_start, name, t_end in key_sigs: - fifths, mode = key_name_to_fifths_mode(name) - t_start, t_end = int(t_start), int(t_end) - part.add(score.KeySignature(fifths, mode), t_start, t_end) - warnings.warn("add notes", stacklevel=2) + # key sig + if len(key_sigs) > 0: + for t_start, name, t_end in key_sigs: + fifths, mode = key_name_to_fifths_mode(name) + t_start, t_end = int(t_start), int(t_end) + part.add(score.KeySignature(fifths, mode), t_start, t_end) + else: + warnings.warn("No key signatures added") + + + # time sig + if len(time_sigs) > 0: + for ts_start, num, den, ts_end in time_sigs: + time_sig = score.TimeSignature(num.item(), den.item()) + part.add(time_sig, ts_start, ts_end) + else: + warnings.warn("No time signatures added") + # without time signature, no measures + barebones = True + warnings.warn("add notes", stacklevel=2) + # add the notes for n in note_array: if n["duration_div"] > 0: note = score.Note( @@ -110,30 +163,12 @@ def create_part( part.add(note, n["onset_div"], n["onset_div"] + n["duration_div"]) - if np.all(note_array["ts_beats"] == None): - warnings.warn("No time signatures found, assuming 4/4") - time_sigs = [[0, 4, 4]] - else: - time_sigs = [[0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"]]] - for n in note_array: - if n["ts_beats"] != time_sigs[-1][1] or n["ts_beat_type"] != time_sigs[-1][2]: - time_sigs.append([n["onset_div"], n["ts_beats"], n["ts_beat_type"]]) - time_sigs = np.array(time_sigs) + warnings.warn("add measures", stacklevel=2) - # for convenience we add the end times for each time signature - ts_end_times = np.r_[time_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] - time_sigs = np.column_stack((time_sigs, ts_end_times)) - - warnings.warn("add time sigs and measures", stacklevel=2) - - for ts_start, num, den, ts_end in time_sigs: - time_sig = score.TimeSignature(num.item(), den.item()) - part.add(time_sig, ts_start, ts_end) - - if anacrusis_divs > 0: + if not barebones and anacrusis_divs > 0: part.add(score.Measure(0), 0, anacrusis_divs) - if sanitize: + if not barebones and sanitize: warnings.warn("Inferring measures", stacklevel=2) score.add_measures(part) @@ -146,6 +181,9 @@ def create_part( # apply simplistic tuplet finding heuristic score.find_tuplets(part) + # clean up + score.sanitize_part(part) + warnings.warn("done create_part", stacklevel=2) return part @@ -155,26 +193,61 @@ def note_array_to_score( name_id: str = "", divs: int = None, key_sigs: list = None, + time_sigs: list = None, part_name: str = "", assign_note_ids: bool = True, - ensurelist: bool = False, estimate_key: bool = False, + estimate_time: bool = False, sanitize: bool = True, return_part: bool = False) -> ScoreLike: """ A generic function to transform an enriched note_array to part or Score. The function can be used for many different occasions, i.e. part_from_graph, part from note_array, part from midi score import, etc. - This function requires a note array that contains time signatures and key signatures(optional - can also estimate it automatically). + This function requires a note array that contains time signatures and key signatures (optional - can also estimate it automatically). Note array should contain the following fields: - onset_div or onset_beat - duration_div or duration_beat - pitch - - ts_beats - - ts_beat_type - - key_mode(optional) - - key_fifths(optional) - - id(required but can also be empty) + + For time signature and key signature the arguments are processed in the following hierarchy: + + Key sig: (["key_fifths", "key_mode"] fields) overrides (key_sigs list) overrides (estimate_key bool) + Time sig: (["ts_beats", "ts_beat_type"] fields) overrides (time_sigs list) overrides (estimate_time bool) + + If either times in divs or beats are missing, these cases are assumed: + + Only divs: divs/ticks need to be specified, beats are computed as quarters (not relative to time signature). + Only beats: divs/ticks as well as times in divs are computed assuming the beat times are given in quarters. + + This function thus handles the following cases: + + 1) note_array fields ["onset_beat", "duration_beat", "pitch"] + -> barebones part, divs estimated assuming uniform beats in quarters + + estimate_time -> 4/4 time signature + + estimate_key -> barebones + estimate key signature + + time_sigs -> time signatures are added, times assumed in quarters (possible error against div/beat) + + key_sigs -> key signatures are added, times assumed in quarters + + ["ts_beats", "ts_beat_type"] -> time signatures are added (possible error against div/beat) + + ["key_fifths", "key_mode"] -> key signatures are added + + 2) note_array fields ["onset_div", "duration_div", "pitch"] + -> barebones part, uniform beats in quarters estimated from beats + + estimate_time -> 4/4 time signature + + estimate_key -> barebones + estimate key signature + + time_sigs -> time signatures are added, times assumed in divs (possible error against div/beat) + + key_sigs -> key signatures are added, times assumed in divs + + ["ts_beats", "ts_beat_type"] -> time signatures are added (possible error against div/beat) + + ["key_fifths", "key_mode"] -> key signatures are added + + 3) note_array fields ["onset_div", "duration_div", "onset_beat", "duration_beat", "pitch"] + -> barebones part + + estimate_time -> 4/4 time signature (possible error against div/beat) + + estimate_key -> barebones + estimate key signature + + time_sigs -> time signatures are added, times assumed in divs (possible error against div/beat) + + key_sigs -> key signatures are added, times assumed in divs + + ["ts_beats", "ts_beat_type"] -> time signatures are added (possible error against div/beat) + + ["key_fifths", "key_mode"] -> key signatures are added Parameters ---------- @@ -183,21 +256,28 @@ def note_array_to_score( - onset_div or onset_beat - duration_div or duration_beat - pitch - - ts_beats - - ts_beat_type + - ts_beats (optional) + - ts_beat_type (optional) - key_mode(optional) - key_fifths(optional) - - id(required but can also be empty) + - id (optional) divs : int (optional) - Necessary Provided divs for midi import. - key_sigs : list (optional) - List of key signatures. Each key signature is a list of [onset_div, key_name, end_div]. - assign_note_ids : bool (optional) - Assign note_ids. - ensurelist: bool (optional) - ensure that output part is a list. + Divs/ticks per quarter note. + If not given, it is estimated assuming a beats in quarters. + key_sigs: list (optional) + A list of key signatures. Each key signature is a tuple of the form (onset, key_name, offset). + Overridden by note_array fields "key_mode" and "key_fifths". + Overrides estimate_key. + time_sigs: list (optional) + A list of time signatures. Each time signature is a tuple of the form (onset, ts_num, ts_den, offset). + Overridden by note_array fields "key_mode" and "key_fifths". + Overrides estimate_time. estimate_key: bool (optional) - estimate key from note_array. + Estimate a single key signature. + estimate_time: bool (optional) + Add a default time signature. + assign_note_ids: bool (optional) + Assign note_ids. sanitize: bool (optional) sanitize the part by adding measures, tying notes, and finding tuplets. return_part: bool (optional) @@ -205,77 +285,128 @@ def note_array_to_score( Returns ------- - part : list or Part or PartGroup - Maybe should return score + part or score : Part or Score + a Part object or a Score object, depending on return_part. """ + if isinstance(note_array, list): parts = [ - note_array_to_score(note_array=x, name_id=str(i), assign_note_ids=assign_note_ids, ensurelist=ensurelist, return_part=True) for + note_array_to_score(note_array=x, + name_id=str(i), + assign_note_ids=assign_note_ids, + return_part=True) for i, x in enumerate(note_array)] return score.Score(partlist=parts) + # Input validation if not isinstance(note_array, np.ndarray): raise TypeError("The note array does not have the correct format.") if len(note_array) == 0: raise ValueError("The note array is empty.") + dtypes = note_array.dtype.names + + ts_case = ["ts_beats", "ts_beat_type"] + ks_case = ["key_fifths", "key_mode"] + + case1 = ["onset_beat", "duration_beat", "pitch"] + case1_ex = ["onset_div", "duration_div"] + case2 = ["onset_div", "duration_div", "pitch"] + case2_ex = ["onset_beat", "duration_beat"] + # case3 = ["onset_div", "duration_div", "onset_beat", "duration_beat", "pitch"] + + # case 1, estimate divs + if all([x in dtypes for x in case1] and [x not in dtypes for x in case1_ex]): + # estimate onset_divs and duration_divs, assumes all beat times as quarters + note_array, divs_ = create_divs_from_beats(note_array) + if divs is not None and divs != divs_: + raise ValueError("estimated divs don't correspond to input divs") + else: + divs = divs_ + + # case 1: convert key sig times to divs + if key_sigs is not None: + key_sigs = np.array(key_sigs) + if key_sigs.shape[1] == 2: + key_sigs[:,0] = (key_sigs[:,0] / divs).astype(int) + elif key_sigs.shape[1] == 3: + key_sigs[:,0] = (key_sigs[:,0] / divs).astype(int) + key_sigs[:,2] = (key_sigs[:,2] / divs).astype(int) + else: + raise ValueError("key_sigs is given in a wrong format") + + # case 1: convert time sig times to divs + if time_sigs is not None: + time_sigs = np.array(time_sigs) + if time_sigs.shape[1] == 3: + time_sigs[:,0] = (time_sigs[:,0] / divs).astype(int) + elif time_sigs.shape[1] == 4: + time_sigs[:,0] = (time_sigs[:,0] / divs).astype(int) + time_sigs[:,3] = (time_sigs[:,3] / divs).astype(int) + else: + raise ValueError("time_sigs is given in a wrong format") + + # case 2, estimate beats + if all([x in dtypes for x in case2] and [x not in dtypes for x in case2_ex]): + # estimate onset_beats and duration_beats in quarters + if divs is None : + raise ValueError("Divs/ticks need to be specified") + else: + note_array = create_beats_from_divs(note_array, divs) + + if divs is None: + # find first note with nonzero duration (in case score starts with grace_note). + for idx, dur in enumerate(note_array["duration_beat"]): + if dur != 0: + break + divs = int(note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) + + # handle time signatures + if all([x in dtypes for x in ts_case]): + time_sigs = [[0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"]]] + for n in note_array: + if n["ts_beats"] != time_sigs[-1][1] or n["ts_beat_type"] != time_sigs[-1][2]: + time_sigs.append([n["onset_div"], n["ts_beats"], n["ts_beat_type"]]) + global_time_sigs = np.array(time_sigs) + elif time_sigs is not None: + global_time_sigs = time_sigs + elif estimate_time: + global_time_sigs = [[0, 4, 4]] + else: + global_time_sigs = None + + if global_time_sigs is not None: + global_time_sigs = np.array(global_time_sigs) + if global_time_sigs.shape[1] == 3: + # for convenience, we add the end times for each time signature + ts_end_times = np.r_[global_time_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] + global_time_sigs = np.column_stack((global_time_sigs, ts_end_times)) + elif global_time_sigs.shape[1] == 4: + pass + else: + raise ValueError("time_sigs is given in a wrong format") + + # Test Note array for negative durations - if "duration_div" in note_array.dtype.names: - assert np.all(note_array["duration_div"] >= 0), "Note array contains negative durations." - elif "duration_beat" in note_array.dtype.names: - assert np.all(note_array["duration_beat"] >= 0), "Note array contains negative durations." + assert np.all(note_array["duration_div"] >= 0), "Note array contains negative durations." + assert np.all(note_array["duration_beat"] >= 0), "Note array contains negative durations." + + # Test for negative divs + assert np.all(note_array["onset_div"] >= 0), "Negative divs found in note_array." # Note id creation or re-assignment - if "id" not in note_array.dtype.names: + if "id" not in dtypes: note_ids = ["{}n{:4d}".format(name_id, i) for i in range(len(note_array))] note_array = rfn.append_fields(note_array, "id", np.array(note_ids, dtype=' note_array["ts_beats"].max(): - pass - else: - for unique_ons in x: - tmp = note_array[note_array["onset_beat"] == unique_ons] - if not np.all(tmp["global_score_time"] == tmp[0]["global_score_time"]): - renorm_onset_beat = True - break - - if renorm_onset_beat: - tmp = note_array[0]["onset_beat"] - tmp_ts = note_array[0]["ts_beats"] - for idx in range(1, len(note_array)): - if note_array[idx]["onset_beat"] < tmp: - tmp_ts = tmp_ts + note_array[idx]["ts_beats"] if note_array[idx][ - "onset_beat"] + tmp_ts < tmp else tmp_ts - tmp = note_array[idx]["onset_beat"] + tmp_ts - note_array[idx]["onset_beat"] = tmp - note_array = create_divs_from_beats(note_array) - elif all([x in dtypes for x in ["onset_beat", "duration_beat", "pitch"]]): - anacrusis_mask[note_array["onset_beat"] < 0] = True - note_array = create_divs_from_beats(note_array) - elif all([x in dtypes for x in ["onset_div", "pitch", "duration_div"]]): - pass - else: - raise AttributeError("The note array does not include the necessary fields.") + # Order Lexicographically + note_array = note_array[np.lexsort((note_array["onset_div"], note_array["pitch"]))] + # estimate voice if "voice" in dtypes: estimate_voice_info = False part_voice_list = note_array["voice"] @@ -283,11 +414,6 @@ def note_array_to_score( estimate_voice_info = True part_voice_list = np.full(len(note_array), np.inf) - if not all(x in dtypes for x in ['step', 'alter', 'octave']): - warnings.warn("pitch spelling") - spelling_global = analysis.estimate_spelling(note_array) - note_array = rfn.merge_arrays((note_array, spelling_global), flatten=True) - if estimate_voice_info: warnings.warn("voice estimation", stacklevel=2) # TODO: deal with zero duration notes in note_array. @@ -300,32 +426,42 @@ def note_array_to_score( estimated_voices[i] = part_voice note_array = rfn.append_fields(note_array, "voice", np.array(estimated_voices, dtype=int)) - if estimate_key or ('ks_fifths' not in dtypes and 'ks_mode' not in dtypes): - warnings.warn("key estimation", stacklevel=2) + # estimate pitch spelling + if not all(x in dtypes for x in ['step', 'alter', 'octave']): + warnings.warn("pitch spelling") + spelling_global = analysis.estimate_spelling(note_array) + note_array = rfn.merge_arrays((note_array, spelling_global), flatten=True) + + # handle or estimate key signature + if all([x in dtypes for x in ks_case]): + global_key_sigs = [[0, fifths_mode_to_key_name(note_array[0]["ks_fifths"], note_array[0]["ks_mode"])]] + for n in note_array: + global_key_sigs.append([n["onset_div"], fifths_mode_to_key_name(n["ks_fifths"], n["ks_mode"])]) + else: + global_key_sigs = key_sigs + elif key_sigs is not None: + global_key_sigs = key_sigs + elif estimate_key: k_name = analysis.estimate_key(note_array) global_key_sigs = [[0, k_name]] else: - if key_sigs is None and "ts_beats" in dtypes: - global_key_sigs = [[0, fifths_mode_to_key_name(note_array[0]["ks_fifths"], note_array[0]["ks_mode"])]] - for n in note_array: - if n["ts_beats"] != global_key_sigs[-1][1] or n["ts_beat_type"] != global_key_sigs[-1][2]: - global_key_sigs.append([n["onset_div"], fifths_mode_to_key_name(n["ks_fifths"], n["ks_mode"])]) - else: - global_key_sigs = key_sigs - global_key_sigs = np.array(global_key_sigs) - # for convenience, we add the end times for each time signature - ks_end_times = np.r_[global_key_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] - global_key_sigs = np.column_stack((global_key_sigs, ks_end_times)) - - # compute quarter divs - if divs is None: - # find first note with nonzero duration (in case score starts with grace_note). - for idx, dur in enumerate(note_array["duration_beat"]): - if dur != 0: - break - divs = int((note_array[idx]["duration_div"] / note_array[idx]["duration_beat"])*(note_array[idx]["ts_beat_type"]/4)) - + global_key_sigs = None + + if global_key_sigs is not None: + global_key_sigs = np.array(global_key_sigs) + if global_key_sigs.shape[1] == 2: + # for convenience, we add the end times for each time signature + ks_end_times = np.r_[global_key_sigs[1:, 0], np.max(note_array["onset_div"]+note_array["duration_div"])] + global_key_sigs = np.column_stack((global_key_sigs, ks_end_times)) + elif global_key_sigs.shape[1] == 3: + pass + else: + raise ValueError("key_sigs is given in a wrong format") + # Steps for dealing with anacrusis measure. + anacrusis_mask = np.zeros(len(note_array), dtype=bool) + anacrusis_mask[note_array["onset_beat"] < 0] = True + if np.all(anacrusis_mask == False): anacrusis_divs = 0 else: @@ -334,17 +470,13 @@ def note_array_to_score( beat_type = np.max(note_array[anacrusis_mask]["ts_beat_type"]) difference_from_zero = (0 - last_neg_beat) * divs * (4 / beat_type) anacrusis_divs = int(last_neg_divs + difference_from_zero) - - # Test again for negative divs - assert np.all(note_array["onset_div"] >= 0), "Negative divs found in note_array." - # Order Lexicographically - note_array = note_array[np.lexsort((note_array["onset_div"], note_array["pitch"]))] - + # Create the part part = create_part( ticks=divs, note_array=note_array, key_sigs=global_key_sigs, + time_sigs=global_time_sigs, part_id=name_id, part_name=part_name, sanitize=sanitize, From 8b2cde33f3c2682a2692061f969946d691e53f03 Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 15:39:37 +0200 Subject: [PATCH 080/122] typo --- partitura/musicanalysis/note_array_to_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 59a5ddb8..925ff4fc 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -283,7 +283,7 @@ def note_array_to_score( sanitize: bool (optional) sanitize the part by adding measures, tying notes, and finding tuplets. return_part: bool (optional) - Return a Partitura score object instead of a part. + Return a Partitura part object instead of a score. Returns ------- From c0b11a4a1676ae50250698871401a759d0a7cd6c Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 16:07:46 +0200 Subject: [PATCH 081/122] imporve div estimation if time sig field is present, tests check --- .../musicanalysis/note_array_to_score.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 925ff4fc..ccecfe12 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -71,8 +71,8 @@ def create_beats_from_divs(note_array: np.ndarray, divs: int): def create_part( ticks: int, note_array: np.ndarray, - key_sigs: list = [], - time_sigs: list = [], + key_sigs: list = None, + time_sigs: list = None, part_id: str = None, part_name: str = None, sanitize: bool = True, @@ -121,7 +121,7 @@ def create_part( part.add(clef, 0) # key sig - if len(key_sigs) > 0: + if key_sigs is not None: for t_start, name, t_end in key_sigs: fifths, mode = key_name_to_fifths_mode(name) t_start, t_end = int(t_start), int(t_end) @@ -129,9 +129,8 @@ def create_part( else: warnings.warn("No key signatures added") - # time sig - if len(time_sigs) > 0: + if time_sigs is not None: for ts_start, num, den, ts_end in time_sigs: time_sig = score.TimeSignature(num.item(), den.item()) part.add(time_sig, ts_start, ts_end) @@ -358,10 +357,18 @@ def note_array_to_score( if divs is None: # find first note with nonzero duration (in case score starts with grace_note). - for idx, dur in enumerate(note_array["duration_beat"]): - if dur != 0: - break - divs = int(note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) + if all([x in dtypes for x in ts_case]): + for idx, dur in enumerate(note_array["duration_beat"]): + if dur != 0: + break + divs = int((note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) / + (4 / note_array[idx]["ts_beat_type"] )) + else: + for idx, dur in enumerate(note_array["duration_beat"]): + if dur != 0: + break + divs = int(note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) + # handle time signatures if all([x in dtypes for x in ts_case]): From db53a12ea64f8b39b978b012865c072d1043f1fc Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 16:38:08 +0200 Subject: [PATCH 082/122] bug fixes have starting key and time sigs, use beat_type if not available for anacrusis --- .../musicanalysis/note_array_to_score.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index ccecfe12..691226b6 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -357,16 +357,13 @@ def note_array_to_score( if divs is None: # find first note with nonzero duration (in case score starts with grace_note). + for idx, dur in enumerate(note_array["duration_beat"]): + if dur != 0: + break if all([x in dtypes for x in ts_case]): - for idx, dur in enumerate(note_array["duration_beat"]): - if dur != 0: - break divs = int((note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) / - (4 / note_array[idx]["ts_beat_type"] )) - else: - for idx, dur in enumerate(note_array["duration_beat"]): - if dur != 0: - break + (4 / note_array[idx]["ts_beat_type"] )) + else: divs = int(note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) @@ -395,6 +392,8 @@ def note_array_to_score( else: raise ValueError("time_sigs is given in a wrong format") + # make sure there is a time signature from the beginning + global_time_sigs[0, 0] = 0 # Test Note array for negative durations assert np.all(note_array["duration_div"] >= 0), "Note array contains negative durations." @@ -466,6 +465,9 @@ def note_array_to_score( else: raise ValueError("key_sigs is given in a wrong format") + # make sure there is a key signature from the beginning + global_key_sigs[0, 0] = 0 + # Steps for dealing with anacrusis measure. anacrusis_mask = np.zeros(len(note_array), dtype=bool) anacrusis_mask[note_array["onset_beat"] < 0] = True @@ -475,9 +477,14 @@ def note_array_to_score( else: last_neg_beat = np.max(note_array[anacrusis_mask]["onset_beat"]) last_neg_divs = np.max(note_array[anacrusis_mask]["onset_div"]) - beat_type = np.max(note_array[anacrusis_mask]["ts_beat_type"]) + if all([x in dtypes for x in ts_case]): + beat_type = np.max(note_array[anacrusis_mask]["ts_beat_type"]) + else: + beat_type = 4 difference_from_zero = (0 - last_neg_beat) * divs * (4 / beat_type) anacrusis_divs = int(last_neg_divs + difference_from_zero) + + # Create the part part = create_part( From 451f0ffdf30508348c24f9a6a7659b822d07606c Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 16:58:45 +0200 Subject: [PATCH 083/122] correct typing for onset_beat estimation --- partitura/musicanalysis/note_array_to_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 691226b6..9cdf1581 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -64,7 +64,7 @@ def create_beats_from_divs(note_array: np.ndarray, divs: int): """ onset_beats = list(note_array["onset_div"]/divs) duration_beats = list(note_array["duration_div"]/divs) - na_beats = np.array(list(zip(onset_beats, duration_beats)), dtype=[("onset_beat", int), ("duration_beat", int)]) + na_beats = np.array(list(zip(onset_beats, duration_beats)), dtype=[("onset_beat", float), ("duration_beat", float)]) return rfn.merge_arrays((note_array, na_beats), flatten=True, usemask=False) From 22b7eb13f5f262e02da3b0a3002ea1c28ddb4925 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Tue, 6 Jun 2023 17:51:02 +0200 Subject: [PATCH 084/122] added the change time signature export mode to midi export added the miditok support, for now it is only working for midi like tokenizers, due to miditok limitations --- partitura/io/exportmidi.py | 71 +++++++- partitura/utils/music.py | 4 +- tests/__init__.py | 4 + tests/data/musicxml/test_polyphonic.xml | 219 ++++++++++++++++++++++++ tests/test_midi_export.py | 35 +++- tests/test_utils.py | 17 +- 6 files changed, 331 insertions(+), 19 deletions(-) create mode 100644 tests/data/musicxml/test_polyphonic.xml diff --git a/partitura/io/exportmidi.py b/partitura/io/exportmidi.py index f256ec80..9021a708 100644 --- a/partitura/io/exportmidi.py +++ b/partitura/io/exportmidi.py @@ -233,6 +233,7 @@ def save_score_midi( part_voice_assign_mode: int = 0, velocity: int = 64, anacrusis_behavior: str = "shift", + minimum_ppq: int = 0, ) -> Optional[MidiFile]: """Write data from Part objects to a MIDI file @@ -276,11 +277,20 @@ def save_score_midi( The default mode is 0. velocity : int, optional Default velocity for all MIDI notes. Defaults to 64. - anacrusis_behavior : {"shift", "pad_bar"}, optional + anacrusis_behavior : {"shift", "pad_bar", "time_sig_change"}, optional Strategy to deal with anacrusis. If "shift", all time points are shifted by the anacrusis (i.e., the first note starts at 0). If "pad_bar", the "incomplete" bar of the anacrusis is padded with silence. Defaults to 'shift'. + If "time_sig_change", the time signature is changed to match + the duration of the measure. This also ensure the beat and + downbeats position are coherent in case of incomplete measures + later in the score. + minimum_ppq : int, optional + Minimum ppq to use for the MIDI file. If the ppq of the score is less, + it will be doubled until it is above the threshold. This is useful + because some libraries like miditok require a certain minimum ppq to + work properly. Returns ------- @@ -302,6 +312,10 @@ def save_score_midi( f" or a list of `Part` instances but is {type(score_data)}" ) ppq = get_ppq(parts) + # double it until it is above the minimum level. + # Doubling instead of setting it ensure that the common divisors stay the same. + while ppq < minimum_ppq: + ppq = ppq*2 events = defaultdict(lambda: defaultdict(list)) meta_events = defaultdict(lambda: defaultdict(list)) @@ -316,7 +330,7 @@ def save_score_midi( ftp = 0 # Deal with anacrusis if first_time_point < 0: - if anacrusis_behavior == "shift": + if anacrusis_behavior == "shift" or anacrusis_behavior == "time_sig_change": ftp = first_time_point elif anacrusis_behavior == "pad_bar": time_signatures = [] @@ -346,12 +360,55 @@ def to_ppq(t): "set_tempo", tempo=tp.microseconds_per_quarter ) - for ts in part.iter_all(score.TimeSignature): - meta_events[part][to_ppq(ts.start.t)].append( - MetaMessage( - "time_signature", numerator=ts.beats, denominator=ts.beat_type + if anacrusis_behavior == "time_sig_change": + # Change time signature to match the duration of the measure + # This ensure the beat and downbeats position are coherent + # in case of incomplete measures later in the score. + all_ts = list(part.iter_all(score.TimeSignature)) + ts_changing_time = [ts.start.t for ts in all_ts] + for measure in part.iter_all(score.Measure): + m_duration_beat = part.beat_map(measure.end.t) - part.beat_map(measure.start.t) + m_ts = part.time_signature_map(measure.start.t) + if m_duration_beat != m_ts[0]: + # add ts change + # TODO: add support for changing the beat type if number of beats is not integer + meta_events[part][to_ppq(measure.start.t)].append( + MetaMessage( + "time_signature", numerator=int(m_duration_beat), denominator=int(m_ts[1]) + ) + ) + ts_changing_time.append(measure.start.t) # keep track of changing the ts + # now go back to original ts if there is no ts change after this measure + if not any([ts_t > measure.start.t for ts_t in ts_changing_time]): + meta_events[part][to_ppq(measure.end.t)].append( + MetaMessage( + "time_signature", numerator=int(m_ts[0]), denominator=int(m_ts[1]) + ) + ) + # filter out the multiple ts changes at the same time + # this happens when multiple measure in a row have wrong duration + for t in meta_events[part].keys(): + if len(meta_events[part][t]) == 2: + meta_events[part][t] = meta_events[part][t][1:] + + # now add the normal time signature change + for ts in part.iter_all(score.TimeSignature): + if ts.start.t in ts_changing_time: + #don't add if something is already added at this time to cover the case of a ts change when the first measure is shorter/longer + pass + else: + meta_events[part][to_ppq(ts.start.t)].append( + MetaMessage( + "time_signature", numerator=ts.beats, denominator=ts.beat_type + ) + ) + else: # just add the time signature that are explicit in partitura + for ts in part.iter_all(score.TimeSignature): + meta_events[part][to_ppq(ts.start.t)].append( + MetaMessage( + "time_signature", numerator=ts.beats, denominator=ts.beat_type + ) ) - ) for ks in part.iter_all(score.KeySignature): meta_events[part][to_ppq(ks.start.t)].append( diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 50cc0f93..aff638a9 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3451,12 +3451,12 @@ def slice_ppart_by_time( return ppart_slice -def tokenize(score_data : ScoreLike, tokenizer : miditok.midi_tokenizer.MidiTokenizer): +def tokenize(score_data : ScoreLike, tokenizer : miditok.midi_tokenizer.MidiTokenizer if miditok is not None else None): if miditok is None: raise ImportError("Miditok must be installed for this function to work") with TemporaryDirectory() as tmpdir: temp_midi_path = os.path.join(tmpdir, "temp_midi.mid") - partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, part_voice_assign_mode = 4 ) + partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, anacrusis_behavior="time_sig_change", part_voice_assign_mode = 4, minimum_ppq = 480 ) midi = miditoolkit.MidiFile(temp_midi_path) tokens = tokenizer(midi) return tokens diff --git a/tests/__init__.py b/tests/__init__.py index b44c78ba..41bbbf30 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -220,3 +220,7 @@ "score" : os.path.join(DATA_PATH, "musicxml", "mozart_k265_var1.musicxml"), }, ] + +MIDIEXPORT_TESTFILES = [ + os.path.join(DATA_PATH, "musicxml", "test_anacrusis.xml") +] \ No newline at end of file diff --git a/tests/data/musicxml/test_polyphonic.xml b/tests/data/musicxml/test_polyphonic.xml new file mode 100644 index 00000000..16deade0 --- /dev/null +++ b/tests/data/musicxml/test_polyphonic.xml @@ -0,0 +1,219 @@ + + + + + Untitled score + + + Composer / arranger + + MuseScore 4.0.2 + 2023-06-06 + + + + + + + + + + Piano + Pno. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + 2 + + 0 + + + 2 + + G + 2 + + + F + 4 + + + + + E + 4 + + 4 + 1 + half + up + 1 + + + + + G + 4 + + 4 + 1 + half + up + 1 + + + + 4 + 1 + half + 1 + + + 8 + + + + C + 3 + + 4 + 5 + half + up + 2 + + + + C + 3 + + 1 + 5 + eighth + down + 2 + begin + + + + D + 3 + + 1 + 5 + eighth + down + 2 + continue + + + + E + 3 + + 1 + 5 + eighth + down + 2 + continue + + + + F + 3 + + 1 + 5 + eighth + down + 2 + end + + + + + + C + 5 + + 8 + 1 + whole + 1 + + + 8 + + + + G + 3 + + 4 + 5 + half + down + 2 + + + + C + 3 + + 4 + 5 + half + up + 2 + + + + + + 8 + 1 + 1 + + + 8 + + + + A + 2 + + 4 + 5 + half + up + 2 + + + + 4 + 5 + half + 2 + + + light-heavy + + + + diff --git a/tests/test_midi_export.py b/tests/test_midi_export.py index 077f6f8a..bbc11c03 100644 --- a/tests/test_midi_export.py +++ b/tests/test_midi_export.py @@ -9,12 +9,15 @@ from tempfile import TemporaryFile import mido import numpy as np +from tempfile import TemporaryDirectory +import os -from partitura import save_score_midi, save_performance_midi, load_performance_midi +from partitura import save_score_midi, save_performance_midi, load_performance_midi, load_score from partitura.utils import partition import partitura.score as score from partitura.performance import PerformedPart, Performance +from tests import MIDIEXPORT_TESTFILES LOGGER = logging.getLogger(__name__) @@ -479,3 +482,33 @@ def generate_random_performance(n_notes=100, beat_period=0.5, n_tracks=3): performed_part = PerformedPart.from_note_array(note_array) return performed_part + +class TestIncompleteMeasures(unittest.TestCase): + def test_timesigchange(self): + # test the behavior with the time_sig_change parameter in midi export + score_data = load_score(MIDIEXPORT_TESTFILES[0]) + with TemporaryDirectory() as tmpdir: + temp_midi_path = os.path.join(tmpdir, "temp_midi.mid") + save_score_midi(score_data, out=temp_midi_path, anacrusis_behavior="time_sig_change", part_voice_assign_mode = 4 ) + mid_pt = mido.MidiFile(temp_midi_path) + ts_messages = [m for m in list(mid_pt.tracks[0]) if isinstance(m,mido.MetaMessage) and m.type == "time_signature"] + self.assertTrue(len(ts_messages)==5) + self.assertTrue(ts_messages[0].numerator == 1) + self.assertTrue(ts_messages[1].numerator == 4) + self.assertTrue(ts_messages[2].numerator == 3) + self.assertTrue(ts_messages[3].numerator == 1) + self.assertTrue(ts_messages[4].numerator == 4) + + def test_pad_bar(self): + # test the behavior with the pad_bar parameter in midi export + score_data = load_score(MIDIEXPORT_TESTFILES[0]) + with TemporaryDirectory() as tmpdir: + temp_midi_path = os.path.join(tmpdir, "temp_midi.mid") + save_score_midi(score_data, out=temp_midi_path, anacrusis_behavior="pad_bar", part_voice_assign_mode = 4 ) + mid_pt = mido.MidiFile(temp_midi_path) + ts_messages = [m for m in list(mid_pt.tracks[0]) if isinstance(m,mido.MetaMessage) and m.type == "time_signature"] + self.assertTrue(len(ts_messages)==1) + note_on_messages = [m for m in list(mid_pt.tracks[0]) if isinstance(m,mido.Message) and m.type == "note_on"] + # TODO: check why the first note is still on a downbeat + + diff --git a/tests/test_utils.py b/tests/test_utils.py index 8dabd806..a2494559 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -600,12 +600,11 @@ def test_interp1d(self): self.assertTrue(np.all(sinterp(x) == pinterp(x))) -class TestTokenizer(unittest.TestCase): - def test_sametokens1(self): - """ Test that the tokenizer called from the partitura wrapper returns the same tokens as the Miditok from MIDI""" - pt_score = partitura.load_score(TOKENIZER_TESTFILES[0]["score"]) - tokenizer = miditok.Structured() - pt_tokens = tokenize(pt_score, tokenizer) - midi_score = miditoolkit.MidiFile(TOKENIZER_TESTFILES[0]["midi"]) - mtok_tokens = tokenizer(midi_score) - self.assertTrue(False) \ No newline at end of file +# class TestTokenizer(unittest.TestCase): +# def test_tokenize1(self): +# """ Test the partitura tokenizer""" +# pt_score = partitura.load_score(TOKENIZER_TESTFILES[0]["score"]) +# tokenizer = miditok.MIDILike() +# pt_tokens = tokenize(pt_score, tokenizer)[0].tokens +# mtok_tokens = [tok for tok in mtok_tokens if not tok.startswith("Velocity")] +# self.assertTrue(mtok_tokens[:20] == pt_tokens[:20]) \ No newline at end of file From 43d68c4f91a0ea18c53c225b7b2b878c20c9efee Mon Sep 17 00:00:00 2001 From: Patricia Hu Date: Tue, 6 Jun 2023 17:52:37 +0200 Subject: [PATCH 085/122] minor changes, added docstrings --- partitura/io/importmusicxml.py | 51 ++++++++++++++++++++++++++-------- partitura/score.py | 1 + 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index e75f885a..f93466f7 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -303,7 +303,6 @@ def load_musicxml( def _parse_parts(document, part_dict): - # print('_parse_parts') # NOTE del """ Populate the Part instances that are the values of `part_dict` with the musical content in document. @@ -457,11 +456,32 @@ def _parse_parts(document, part_dict): def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_counter): - # print('_handle_measure') # NOTE del - """ - Parse a ... element, adding it and its contents to the - part. + """ Parse a ... element, adding it and its contents to the part. + + Parameters + ---------- + measure_el : etree.Element + An etree Element instance with a tag + position : int + The starting time of the measure + part : score.Part + The part object to which the measure belongs. + ongoing : dict + A dict specifying the score.Page and score.System of the measure + doc_order : int + The index of the first note element in the current measure in the xml file. + measure_counter : int + The index of the tag in the xml file, starting from 1 + + Returns + ------- + measure_maxtime : int + The ending time of the measure + doc_order : int + The index of the first note element in the next measure in the xml file. + """ + # make a measure object measure = make_measure(measure_el, measure_counter) @@ -588,7 +608,7 @@ def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_coun # add end time of measure part.add(measure, None, measure_maxtime) - + return measure_maxtime, doc_order @@ -698,16 +718,25 @@ def _handle_new_system(position, part, ongoing): def make_measure(xml_measure, measure_counter): + """ + Parameters + ---------- + xml_measure : etree.Element + An etree Element instance with a tag + measure_counter : int + The index of the tag in the xml file, starting from 1 + + Returns + ------- + measure : score.Measure + A measure object with a number and optional name attribute + """ + measure = score.Measure() - # try: - # measure.number = int(xml_measure.attrib['number']) - # except: - # LOGGER.warn('No number attribute found for measure') measure.number = measure_counter measure.name = get_value_from_attribute(xml_measure, "number", str) - # print(f'{measure}') return measure diff --git a/partitura/score.py b/partitura/score.py index 86335a03..5b463919 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -2421,6 +2421,7 @@ def __init__(self, number=None, name=None): self.name = name def __str__(self): + # return f"{super().__str__():16s} number={str(self.number):3s} name={self.name:3s}" return f"{super().__str__():16s} number={self.number} name={self.name}" @property From 248397608a0c9e3391fbf85930b45ab465ae3b75 Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 18:32:55 +0200 Subject: [PATCH 086/122] added several test cases for note_array_to_score --- tests/test_note_array.py | 66 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/test_note_array.py b/tests/test_note_array.py index 68f9c141..3f6489d0 100644 --- a/tests/test_note_array.py +++ b/tests/test_note_array.py @@ -87,6 +87,72 @@ def test_note_array_to_score_anacrusis(self): self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"])) self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + def test_note_array_to_score_only_divs(self): + score = load_musicxml(METRICAL_POSITION_TESTFILES[1]) + note_array = score.note_array(include_time_signature=True) + for div in [1,2,4,8]: + new_score = note_array_to_score(note_array[["onset_div", "duration_div", "pitch"]],divs = div) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_div"]/div)) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_div"]/div)) + + def test_note_array_to_score_only_div_time_sig(self): + score = load_musicxml(METRICAL_POSITION_TESTFILES[1]) + note_array = score.note_array(include_time_signature=True) + for time_sig in [4,8,9]: + new_score = note_array_to_score(note_array[["onset_div", "duration_div", "pitch"]],divs = 1, time_sigs = [(2,4, time_sig)]) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["ts_beat_type"] == np.ones_like(note_array["onset_div"])*time_sig)) + + def test_note_array_to_score_only_beats(self): + score = load_musicxml(METRICAL_POSITION_TESTFILES[1]) + note_array = score.note_array(include_time_signature=True) + # without time signature -> barebones score without measures/anacrusis + new_score = note_array_to_score(note_array[["onset_beat", "duration_beat", "pitch"]]) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"]+1.0)) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + + # with time signature -> measures/anacrusis kept, but div/beat ratio might change + new_score = note_array_to_score(note_array[["onset_beat", "duration_beat", "pitch"]], time_sigs= [(1,3,8)] ) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"]*2)) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"]*2)) + + # with default time signature -> measures/anacrusis kept, 4/4 corrsponds to original + new_score = note_array_to_score(note_array[["onset_beat", "duration_beat", "pitch"]], estimate_time = True) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"])) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + + def test_note_array_to_score_beats_and_div(self): + score = load_musicxml(METRICAL_POSITION_TESTFILES[1]) + note_array = score.note_array(include_time_signature=True) + # without time signature -> barebones score without measures/anacrusis + new_score = note_array_to_score(note_array[["onset_div","duration_div","onset_beat", "duration_beat", "pitch"]]) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"]+1.0)) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + + # with time signature -> measures/anacrusis kept, but div/beat ratio might change + new_score = note_array_to_score(note_array[["onset_div","duration_div","onset_beat", "duration_beat", "pitch"]], time_sigs= [(1,3,8)] ) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"]*2)) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"]*2)) + + # with default time signature -> measures/anacrusis kept, 4/4 corrsponds to original + new_score = note_array_to_score(note_array[["onset_div","duration_div","onset_beat", "duration_beat", "pitch"]], estimate_time = True) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"])) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + + # with original time signature -> measures/anacrusis kept, other arguments overridden + new_score = note_array_to_score(note_array[["onset_div","duration_div","onset_beat", "duration_beat", "pitch", "ts_beats", "ts_beat_type"]] , time_sigs= [(1,3,8)], estimate_time = True) + new_note_array = new_score.note_array(include_time_signature=True) + self.assertTrue(np.all(new_note_array["onset_beat"] == note_array["onset_beat"])) + self.assertTrue(np.all(new_note_array["duration_beat"] == note_array["duration_beat"])) + + def test_notearray_ts_beats(self): part = load_musicxml(NOTE_ARRAY_TESTFILES[0])[0] note_array = note_array_from_part(part, include_time_signature=True) From 2b6fd89d811c578406b6def9c86175501933a935 Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 20:45:47 +0200 Subject: [PATCH 087/122] possible fix for incr/decr features --- .github/workflows/partitura_unittests.yml | 2 +- partitura/musicanalysis/note_features.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index d2b4388a..7424d8c1 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -25,7 +25,7 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . - - name: Instal Optional dependencies + - name: Install Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 pip install torch diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index 62bf24c3..44829e58 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -385,6 +385,10 @@ def compute_note_array( feature_data_struct = make_note_feats(part, feature_functions, add_idx=True) note_array_joined = np.lib.recfunctions.join_by("id", na, feature_data_struct) note_array = note_array_joined.data + pitch_sort_idx = np.argsort(note_array["pitch"]) + note_array = note_array[pitch_sort_idx] + onset_sort_idx = np.argsort(note_array["onset_div"], kind="mergesort") + note_array = note_array_joined.data[onset_sort_idx] else: note_array = na return note_array From 64f7a99bcd93aa986ffdec9cae292c254fab4c8d Mon Sep 17 00:00:00 2001 From: sildater Date: Tue, 6 Jun 2023 22:20:44 +0200 Subject: [PATCH 088/122] fix monotonize_times, add tests for performance features --- partitura/musicanalysis/note_features.py | 4 +- partitura/musicanalysis/performance_codec.py | 4 +- .../musicanalysis/performance_features.py | 266 ++---------------- partitura/utils/generic.py | 6 +- tests/__init__.py | 4 + tests/test_performance_expressions.py | 43 ++- 6 files changed, 67 insertions(+), 260 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index 44829e58..e3c08c77 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -103,6 +103,8 @@ def make_note_features( the functions themselves or the names of a feature function as strings (or a mix), or the keywork "all". The feature functions specified by name are looked up in the `featuremixer.featurefunctions` module. + add_idx: bool + add score note idx column to feature array Returns ------- @@ -388,7 +390,7 @@ def compute_note_array( pitch_sort_idx = np.argsort(note_array["pitch"]) note_array = note_array[pitch_sort_idx] onset_sort_idx = np.argsort(note_array["onset_div"], kind="mergesort") - note_array = note_array_joined.data[onset_sort_idx] + note_array = note_array_joined[onset_sort_idx] else: note_array = na return note_array diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 5b8158ac..b88380fb 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -457,7 +457,7 @@ def tempo_by_average( # Monotonize times eq_onset_mt, unique_s_onsets_mt = monotonize_times( - eq_onsets, deltas=unique_s_onsets + eq_onsets, x=unique_s_onsets ) # Estimate Beat Period @@ -561,7 +561,7 @@ def tempo_by_derivative(score_onsets, performed_onsets, # Monotonize times eq_onset_mt, unique_s_onsets_mt = monotonize_times(eq_onsets, - deltas=unique_s_onsets) + x=unique_s_onsets) # Function that that interpolates the equivalent performed onsets # as a function of the score onset. onset_fun = interp1d(unique_s_onsets_mt, eq_onset_mt, kind='linear', diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index fe6bc9df..00b4a5f6 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -36,7 +36,8 @@ class InvalidPerformanceFeatureException(Exception): def compute_performance_features(score: ScoreLike, performance: PerformanceLike, alignment: list, - feature_functions: Union[List, str] + feature_functions: Union[List, str], + add_idx: bool = True ): """ Compute the performance features. This function is defined in the same @@ -56,12 +57,14 @@ def compute_performance_features(score: ScoreLike, strings (or a mix). currently implemented: asynchrony_feature, articulation_feature, dynamics_feature, pedal_feature + add_idx: bool + add score note idx column to feature array Returns ------- performance_features : structured array """ - m_score, unique_onset_idxs = compute_matched_score(score, performance, alignment) + m_score, unique_onset_idxs, snote_ids = compute_matched_score(score, performance, alignment) acc = [] if isinstance(feature_functions, str) and feature_functions == "all": @@ -108,8 +111,17 @@ def compute_performance_features(score: ScoreLike, acc.append(features) + if add_idx: + acc.append(np.array([(idx) for idx in snote_ids], dtype = [("id","U256")])) + performance_features = rfn.merge_arrays(acc, flatten=True, usemask=False) - return performance_features + full_performance_features = rfn.join_by("id", performance_features, m_score) + full_performance_features = full_performance_features.data + pitch_sort_idx = np.argsort(full_performance_features["pitch"]) + full_performance_features = full_performance_features[pitch_sort_idx] + onset_sort_idx = np.argsort(full_performance_features["onset"], kind="mergesort") + full_performance_features = full_performance_features[onset_sort_idx] + return full_performance_features def compute_matched_score(score: ScoreLike, @@ -133,7 +145,7 @@ def compute_matched_score(score: ScoreLike, unique_onset_idxs : list """ - m_score, _ = to_matched_score(score, performance, alignment, include_score_markings=True) + m_score, snote_ids = to_matched_score(score, performance, alignment, include_score_markings=True) (time_params, unique_onset_idxs) = encode_tempo( score_onsets=m_score["onset"], performed_onsets=m_score["p_onset"], @@ -143,20 +155,8 @@ def compute_matched_score(score: ScoreLike, tempo_smooth="average" ) m_score = rfn.append_fields(m_score, "beat_period", time_params['beat_period'], "f4", usemask=False) - - dyn_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "loudness" in name] - constant_dyn = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), dyn_fields).flatten() - - # process the dynamcis value into discrete markings on the first beat instead of a ramp. - constant_dyn = process_discrete_dynamics(constant_dyn) - - art_fields = [(i, name) for i, name in enumerate(m_score.dtype.names) if "articulation" in name] - articulation = np.apply_along_axis(map_fields, 1, rfn.structured_to_unstructured(m_score), art_fields).flatten() - - m_score = rfn.rec_append_fields(m_score, "constant_dyn", constant_dyn, "U256") - m_score = rfn.rec_append_fields(m_score, "articulation", articulation, "U256") - - return m_score, unique_onset_idxs + m_score = rfn.append_fields(m_score, "id", snote_ids, "U256", usemask=False) + return m_score, unique_onset_idxs, snote_ids def list_performance_feats_functions(): @@ -209,6 +209,7 @@ def print_performance_feats_functions(): list_performance_feature_functions = list_performance_feats_functions print_performance_feature_functions = print_performance_feats_functions + def map_fields(note_info, fields): """ map the one-hot fields of dynamics and articulation marking into one column with field. @@ -229,10 +230,10 @@ def map_fields(note_info, fields): ### Asynchrony + def asynchrony_feature(m_score: np.ndarray, unique_onset_idxs: list, - performance: PerformanceLike, - v=False): + performance: PerformanceLike): """ Compute the asynchrony attributes from the alignment. @@ -294,141 +295,6 @@ def asynchrony_feature(m_score: np.ndarray, ### Dynamics -def dynamics_feature(m_score : np.ndarray, - unique_onset_idxs: list, - performance: PerformanceLike, - window : int = 3, - agg : str = "avg"): - """ - Compute the dynamics attributes from the alignment. - - Parameters - ---------- - m_score : list - correspondance between score and performance notes, with score markings. - unique_onset_idxs : list - a list of arrays with the note indexes that have the same onset - performance: PerformedPart - The original PerformedPart object - window : int - Length of window to look at the range of dynamic marking coverage, default 3. - agg : string - how to aggregate velocity of notes with the same marking, default avg - - Returns - ------- - dynamics_ : structured array (broadcasted to the note level) with the following fields - agreement [-1, 1]: for each pair of dynamics, whether it agree with the OLS. Default 0 - consistency_std [0, 127]: Std of the same marking thoughout the piece. Default 0 - ramp_cor [-1, 1]: The correlation between each dynamics ramp and performed dynamics. Default 0 - tempo_cor [-1, 1]: The correlation between performed dynamics and tempo change. Default 0 - """ - dynamics_ = np.zeros(len(m_score), dtype=[( - "agreement", "f4"), ("consistency_std", "f4"), ("ramp_cor", "f4"), ("tempo_cor", "f4")]) - - # append the marking into m_score based on the time position and windowing - beats_with_constant_dyn = np.unique(m_score[m_score['constant_dyn'] != 'N/A']['onset']) - markings = [m_score[m_score['onset'] == b]['constant_dyn'][0] for b in beats_with_constant_dyn] - # TODO: windowing - velocity = [m_score[m_score['onset'] == b]['velocity'] for b in beats_with_constant_dyn] - velocity_agg = [np.mean(v_group) for v_group in velocity] - - constant_dynamics = list(zip(markings, velocity_agg)) - - # only consider those in the OLS (there exist others like dolce, ) - constant_dynamics = [(m, v) for (m, v) in constant_dynamics if m in OLS] - - if len(constant_dynamics) < 2: - return dynamics_ - - # agreement: compare each adjacent pair of markings with their expected order, average - marking_agreements = [] - for marking1, marking2, beat in zip(constant_dynamics, constant_dynamics[1:], beats_with_constant_dyn[1:]): - (m1, v1), (m2, v2) = marking1, marking2 - m1_, m2_ = OLS.index(m1), OLS.index(m2) - # preventing correlation returning nan when the values are constant - v2 = v2 + 1e-5 - m2_ = m2_ + 1e-5 - tau, _ = stats.kendalltau([v1, v2], [m1_, m2_]) - assert(tau == tau) # not nan - marking_agreements.append((f"{m1}-{m2}", tau)) - dynamics_['agreement'][m_score['onset'] == beat] = tau - - # consistency: how much fluctuations does each marking have - markings = np.array(markings) - velocity_agg = np.array(velocity_agg, dtype=object) - marking_consistency = [] - for marking, beat in zip(np.unique(markings), beats_with_constant_dyn): - marking_std = np.std(np.hstack(velocity_agg[markings == marking])) - marking_consistency.append((f"{marking}", marking_std)) - dynamics_['consistency_std'][m_score['onset'] == beat] = marking_std - - - # changing dynamics - correlation with each incr and decr ramp - (increase_ob, decrease_ob), unique_m_score = parse_changing_ramp(unique_onset_idxs, m_score) - for onset_boundaries, feat_name in zip([increase_ob, decrease_ob], - ['loudness_direction_feature.loudness_incr', 'loudness_direction_feature.loudness_decr']): - for start, end in onset_boundaries: - score_dynamics, performed_dyanmics = [], [] - notes_in_ramp = unique_m_score[(unique_m_score['onset'] >= start) & (unique_m_score['onset'] < end)] - for onset in notes_in_ramp['onset']: - score_dynamics.append(unique_m_score[unique_m_score['onset'] == onset][0][feat_name]) - performed_dyanmics.append(m_score[m_score['onset'] == onset]['velocity'].mean()) - - performed_bp = notes_in_ramp['beat_period'] - performed_bp = performed_bp - performed_bp.min() - performed_dyanmics = np.array(performed_dyanmics) - performed_dyanmics = performed_dyanmics - performed_dyanmics.min() - - cor = stats.pearsonr(score_dynamics, performed_dyanmics)[0] if ( - sum(performed_dyanmics) != 0 and sum(score_dynamics) != 0) else 0 - if "decr" in feat_name: - cor *= -1 - ramp_mask = (m_score['onset'] >= start) & (m_score['onset'] <= end) - dynamics_['ramp_cor'][ramp_mask] = cor - dynamics_['tempo_cor'][ramp_mask] = (-1) * stats.pearsonr(performed_bp, performed_dyanmics)[0] if ( - sum(performed_bp) != 0 and sum(performed_dyanmics) != 0) else 0 - - return dynamics_ - - -def process_discrete_dynamics(constant_dyn): - """reverse the continuous dynamics ramp into discrete marks on the first beat. Rest of the events are filled with N/A""" - constant_dyn_shift = np.append(["N/A"], constant_dyn[:-1]) - positions = np.where(constant_dyn != constant_dyn_shift)[0] - - constant_dyn_ = np.full(len(constant_dyn), "N/A", dtype="U256") - constant_dyn_[positions] = constant_dyn[positions] - - return constant_dyn_ - - -def parse_changing_ramp(unique_onset_idxs, m_score): - """parse the cresceando / decresceando ramp for the actively changing subsequences. - Return a list of (start, end) of the changing subsequences.""" - - unique_m_score = m_score[[idx[0] for idx in unique_onset_idxs]] - - increase, decrease = np.zeros(unique_m_score.shape[0]), np.zeros(unique_m_score.shape[0]) - if 'loudness_direction_feature.loudness_incr' in unique_m_score.dtype.names: - increase = unique_m_score['loudness_direction_feature.loudness_incr'] - if 'loudness_direction_feature.loudness_decr' in unique_m_score.dtype.names: - decrease = unique_m_score['loudness_direction_feature.loudness_decr'] - - onset_boundaries = [] - # finding the increase & decrease boundaries - for ramp in [increase, decrease]: - ramp_diff = np.append(ramp[0], ramp[:-1]) - ramp - has_ramp_diff = ramp_diff != 0 - ramp_boundary = np.append(has_ramp_diff[0], has_ramp_diff[:-1]) ^ has_ramp_diff - onset_boundary = unique_m_score[ramp_boundary]['onset'] - - if len(onset_boundary) % 2 != 0: - onset_boundary = onset_boundary[:-1] - - onset_boundaries.append([(onset_boundary[i], onset_boundary[i+1]) for i in range(0, len(onset_boundary), 2)]) - - return tuple(onset_boundaries), unique_m_score ### Articulation @@ -515,6 +381,7 @@ def get_kor(e1, e2): return min(kor, 5) + def get_next_note(note_info, match_voiced): """ get the next note in the same voice that's a reasonable transition @@ -586,6 +453,7 @@ def pedal_feature(m_score : list, return rfn.merge_arrays([onset_offset_pedals, release_times], flatten=True, usemask=False) + def pedal_ramp(ppart: PerformedPart, m_score: np.ndarray): """Pedal ramp in the same shape as the m_score. @@ -615,94 +483,6 @@ def pedal_ramp(ppart: PerformedPart, return np.array([tuple(i) for i in W], dtype=[("onset_value", "f4"), ("offset_value", "f4")]), agg_ramp_func - ### Phrasing - -def freiberg_kinematic(params, xdata, ydata): - w, q = params - return ydata - (1 + (w ** q - 1) * xdata) ** (1/q) - - -def get_phrase_end(m_score, unique_onset_idxs): - """ - Returns a list of possible phrase endings for analyzing slowing down structure. - (current implementation only takes last 4 beats, need for more advanced segmentation algorithm.) - - Parameters - ---------- - m_score : structured array - correspondance between score and performance notes, with score markings. - unique_onset_idxs : list - a list of arrays with the note indexes that have the same onset - - Returns - ------- - endings : list - list of tuples with (beats, tempo) - """ - beat_first_note = [group[0] for group in unique_onset_idxs] - m_score_beats = m_score[beat_first_note] - - # last 4 beats - final_beat = m_score_beats['onset'][-1] - prase_ending = m_score_beats[(m_score_beats['onset'] >= final_beat - 4)] - xdata, ydata = prase_ending['onset'], 60 / prase_ending['beat_period'] - - endings = [(xdata, ydata)] - return endings - - -def phrasing_attributes(m_score, unique_onset_idxs, plot=False): - """ - Unfinished! after finishing will update to phrasing_feature - rubato: - Model the final tempo curve (last 2 measures) using Friberg & Sundberg’s kinematic model: - (https://www.researchgate.net/publication/220723460_Evidence_for_Pianist-specific_Rubato_Style_in_Chopin_Nocturnes) - v(x) = (1 + (w^q − 1) * x)^(1/q), - w: the final tempo (normalized between 0 and 1, assuming normalized ) - q: variation in curvature - - Parameters - ---------- - m_score : structured array - correspondance between score and performance notes, with score markings. - unique_onset_idxs : list - a list of arrays with the note indexes that have the same onset - - Returns - ------- - pharsing_ : structured array - structured array on the (phrase?) level with fields w and q. - """ - - endings = get_phrase_end(m_score, unique_onset_idxs) - phrasing_ = np.zeros(len(endings), dtype=[("rubato_w", "f4"), ("rubato_q", "f4")]) - - for i, ending in enumerate(endings): - xdata, ydata = ending - - # normalize x and y. y: initial tempo as 1 - xdata = (xdata - xdata.min()) * (1 / (xdata.max() - xdata.min())) - ydata = (ydata - 0) * (1 / (ydata.max() - 0)) - - params_init = np.array([0.5, -1]) - res = least_squares(freiberg_kinematic, params_init, args=(xdata, ydata)) - - w, q = res.x - phrasing_[i]['rubato_w'] = w - phrasing_[i]['rubato_q'] = q - - if plot: - plt.scatter(xdata, ydata, marker="+", c="red") - xline = np.linspace(0, 1, 100) - plt.plot(xline, (1 + (w ** q - 1) * xline) ** (1/q)) - plt.ylim(0, 1.2) - plt.title(f"Friberg & Sundberg kinematic rubato curve with w={round(w, 2)} and q={round(q, 2)}") - plt.show() - - return phrasing_ - - ### Tempo - diff --git a/partitura/utils/generic.py b/partitura/utils/generic.py index 844d218d..35c9e7ca 100644 --- a/partitura/utils/generic.py +++ b/partitura/utils/generic.py @@ -483,7 +483,7 @@ def interp1d( axis: int = -1, kind: Union[str, int] = "linear", copy=True, - bounds_error=None, + bounds_error=False, fill_value=np.nan, assume_sorted=False, ) -> Callable[[Union[float, int, np.ndarray]], np.ndarray]: @@ -644,13 +644,11 @@ def monotonize_times( _x = np.r_[np.min(x) - eps, x, np.max(x) + eps] else: _x = np.r_[-eps, np.arange(len(s)), len(s) - 1 + eps] - idx = np.arange(_s.shape[0]) s_mono = np.maximum.accumulate(s) mask = np.r_[False, True, (np.diff(s_mono) != 0), False] - x_mono = _x[1:-1] - s_mono = interp1d(idx[mask], _s[mask])(x_mono) + s_mono = interp1d(_x[mask], _s[mask], fill_value="extrapolate")(x_mono) return s_mono, x_mono diff --git a/tests/__init__.py b/tests/__init__.py index 2092df1c..14ace011 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -82,6 +82,10 @@ os.path.join(MATCH_PATH, fn) for fn in ["test_fuer_elise.match"] ] +MATCH_EXPRESSIVE_FEATURES_TESTFILES= [ + os.path.join(MATCH_PATH, fn) for fn in ["Chopin_op10_no3_p01.match"] +] + # This is a list of files for testing Nakamura et al.'s corresp and match file loading NAKAMURA_IMPORT_TESTFILES = [ os.path.join(NAKAMURA_PATH, fn) diff --git a/tests/test_performance_expressions.py b/tests/test_performance_expressions.py index 51203409..b67ff52c 100644 --- a/tests/test_performance_expressions.py +++ b/tests/test_performance_expressions.py @@ -6,7 +6,7 @@ import unittest import numpy as np from partitura import load_match -from tests import MATCH_IMPORT_EXPORT_TESTFILES +from tests import MATCH_EXPRESSIVE_FEATURES_TESTFILES from partitura.musicanalysis.performance_features import compute_performance_features import os @@ -14,12 +14,35 @@ class TestPerformanceFeatures(unittest.TestCase): def test_performance_features(self): - for fn in MATCH_IMPORT_EXPORT_TESTFILES: - print(fn) - perf, alignment, score = load_match(filename=fn, create_score=True) - features = compute_performance_features(score, - perf, - alignment, - feature_functions = "all") - - self.assertTrue(True, f"The expression features don't match the original.") + + True_array = np.array([('n1', 0.23374297, 0. , 0. , 0. , 0. , 89.74999 , 62.000057, 0., 0.16015087, -0.5, 0.5 , 59, 4.9925 , 0.8775 , 44, 1, 0., 0., 1.4700003), + ('n4', 0.03011051, 0.07375002, -0.20350015, 0.3837298, 0.03447518, 114.25004 , 61.000244, 0., 0.4027142 , 0. , 1. , 40, 5.7025 , 2.4375 , 22, 7, 0., 0., 2.8474998), + ('n3', 2.527984 , 0.07375002, -0.20350015, 0.3837298, 0.03447518, 87.500046, 61.000244, 0., 0.4027142 , 0. , 0.25, 56, 5.77625, 2.36375, 26, 3, 0., 0., 2.8474998)], + dtype=[('id', ' Date: Tue, 6 Jun 2023 22:55:48 +0200 Subject: [PATCH 089/122] finish norrmalize, basic example --- .../musicanalysis/performance_features.py | 2 +- partitura/utils/normalize.py | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 00b4a5f6..b4882bfb 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -This module implement a series of mid-level descriptors of the performance expressions: asynchrony, dynamics, articulation, phrasing... +This module implement a series of mid-level descriptors of the performance expressions. Built upon the low-level basis functions from the performance codec. """ diff --git a/partitura/utils/normalize.py b/partitura/utils/normalize.py index 5a114915..cce1faed 100644 --- a/partitura/utils/normalize.py +++ b/partitura/utils/normalize.py @@ -5,11 +5,6 @@ """ import numpy as np -DEFAULT_NORM_FUNCS = { - "FEATURE": {"func": lambda x: x / 127, # some normalization function - "kvargs": {}}, # some keyword arguments - # fill up with all note and performance features -} EPSILON=0.0001 @@ -40,9 +35,9 @@ def range_normalize(array, return np.clip(array, 0, 1) else: return array - -def range_normalize(array, + +def zero_one_normalize(array, min_value = -3.0, max_value = 3.0, log = False, @@ -75,14 +70,24 @@ def minmaxrange_normalize(array): return (array - array.min()) / (array.max() - array.min()) -def normalize(array, +DEFAULT_NORM_FUNCS = { + "pitch": {"func": range_normalize, # some normalization function + "kwargs": {"min_value":0, "max_value":127}}, # some keyword arguments + # fill up with all note and performance features +} + + +def normalize(in_array, norm_funcs = DEFAULT_NORM_FUNCS, norm_func_fallback = minmaxrange_normalize, - default_value = -1.0): + default_value = np.inf + ): """ Normalize a note array. May include note features as well as performance features. + All input columns must be of numeric types, everything is + cast to single precision float. Parameters ---------- @@ -96,6 +101,8 @@ def normalize(array, array : np.ndarray The normalized performance array. """ + dtype_new = np.dtype({'names':in_array.dtype.names, 'formats': [float for k in range(len(in_array.dtype.names))]}) + array = in_array.copy().astype(dtype_new) for feature in array.dtype.names: @@ -111,6 +118,6 @@ def normalize(array, array[feature][non_default_mask] = norm_func_fallback(array[feature][non_default_mask]) else: array[feature][non_default_mask] = norm_funcs[feature]["func"](array[feature][non_default_mask], - **norm_funcs[feature]["kvargs"]) + **norm_funcs[feature]["kwargs"]) return array From b011174ef5bb5cc73c142ed74c9f7107a7eed376 Mon Sep 17 00:00:00 2001 From: sildater Date: Wed, 7 Jun 2023 07:27:08 +0200 Subject: [PATCH 090/122] bug fix note feature sorting --- partitura/musicanalysis/note_features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index e3c08c77..f2411ed3 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -390,7 +390,7 @@ def compute_note_array( pitch_sort_idx = np.argsort(note_array["pitch"]) note_array = note_array[pitch_sort_idx] onset_sort_idx = np.argsort(note_array["onset_div"], kind="mergesort") - note_array = note_array_joined[onset_sort_idx] + note_array = note_array[onset_sort_idx] else: note_array = na return note_array From 0d9c90833cc74ea28fe5cb82267686eeb6711585 Mon Sep 17 00:00:00 2001 From: sildater Date: Wed, 7 Jun 2023 08:56:46 +0200 Subject: [PATCH 091/122] normalization safety for constant features --- partitura/utils/normalize.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/partitura/utils/normalize.py b/partitura/utils/normalize.py index cce1faed..886bb9d3 100644 --- a/partitura/utils/normalize.py +++ b/partitura/utils/normalize.py @@ -30,7 +30,11 @@ def range_normalize(array, array = np.log(np.abs(array) + EPSILON) if exp: array = np.exp(array) - array = (array - min_value) / (max_value - min_value) + # handle div by zero + if min_value == max_value: + array = np.clip(array, 0, 1) + else: + array = (array - min_value) / (max_value - min_value) if hard_clip: return np.clip(array, 0, 1) else: @@ -65,9 +69,10 @@ def zero_one_normalize(array, def minmaxrange_normalize(array): """ - Linear mapping a vector from range [min_value, max_value] to [0, 1]. + Linear mapping of a vector from range [array.min(), array.max()] to [0, 1]. + Constant vector is clipped to [0, 1]. """ - return (array - array.min()) / (array.max() - array.min()) + return range_normalize(array) DEFAULT_NORM_FUNCS = { From 702ecda862499152302a4011c63f4ecaae50cb2d Mon Sep 17 00:00:00 2001 From: huispaty Date: Wed, 7 Jun 2023 10:05:43 +0200 Subject: [PATCH 092/122] minor formatting change for Measure object --- partitura/score.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/partitura/score.py b/partitura/score.py index 5b463919..046d264f 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -2421,8 +2421,7 @@ def __init__(self, number=None, name=None): self.name = name def __str__(self): - # return f"{super().__str__():16s} number={str(self.number):3s} name={self.name:3s}" - return f"{super().__str__():16s} number={self.number} name={self.name}" + return f"{super().__str__()} number={self.number} name={self.name}" @property def page(self): From ec49da12456dc2087ac47930efc1bc0a68ee7a0c Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 7 Jun 2023 11:28:14 +0200 Subject: [PATCH 093/122] Fix for issue #261 "none" mode for key signature. --- partitura/io/matchfile_utils.py | 4 ++-- partitura/utils/music.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/partitura/io/matchfile_utils.py b/partitura/io/matchfile_utils.py index 8d4c68c6..1f056aa0 100644 --- a/partitura/io/matchfile_utils.py +++ b/partitura/io/matchfile_utils.py @@ -655,7 +655,7 @@ def fmt(self, fmt: str) -> None: component.fmt = fmt def fifths_mode_to_key_name_v0_1_0(self, fifths: int, mode: str) -> str: - if mode == "major": + if mode in ("major", None, "none", 1): keylist = MAJOR_KEYS suffix = "major" elif mode == "minor": @@ -670,7 +670,7 @@ def fifths_mode_to_key_name_v0_1_0(self, fifths: int, mode: str) -> str: return ks def fifths_mode_to_key_name_v0_3_0(self, fifths: int, mode: str) -> str: - if mode == "major": + if mode in ("major", None, "none", 1): keylist = MAJOR_KEYS suffix = "Maj" elif mode == "minor": diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 24d3ba62..1e03cbb1 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -729,7 +729,7 @@ def fifths_mode_to_key_name(fifths, mode=None): if mode in ("minor", -1): keylist = MINOR_KEYS suffix = "m" - elif mode in ("major", None, 1): + elif mode in ("major", None, "none", 1): keylist = MAJOR_KEYS suffix = "" else: From 165273bb42aa082564b4a8f1af6d23262662f0d9 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 7 Jun 2023 12:04:42 +0200 Subject: [PATCH 094/122] extra information on LeftOutTied. --- partitura/io/importmatch.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index 996edcf3..30ad5ff5 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -679,11 +679,19 @@ def part_from_matchfile( # Check if the note is tied and if so, add the tie information if is_tied: + found = False for el in part.iter_all(end=offset_divs): if isinstance(el, score.Note): - if el.step == note_attributes["step"] and el.octave == note_attributes["octave"]: + condition = el.step == note_attributes["step"] and el.octave == note_attributes["octave"] and el.alter == note_attributes["alter"] + if condition: el.tie_next = part_note part_note.tie_prev = el + found = True + break + if not found: + warnings.warn( + "Tie information found, but no previous note found to tie to for note {}.".format(part_note.id) + ) # add time signatures for (ts_beat_time, ts_bar, tsg) in ts: From 2b17f1909b1f15f2e50ab9f8a6f3220aced4e70c Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 7 Jun 2023 15:03:24 +0200 Subject: [PATCH 095/122] solved a bug in the midi export with pad_bar --- partitura/io/exportmidi.py | 16 ++++++++++++---- partitura/utils/music.py | 2 +- tests/test_midi_export.py | 10 ++++++++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/partitura/io/exportmidi.py b/partitura/io/exportmidi.py index 9021a708..95b95359 100644 --- a/partitura/io/exportmidi.py +++ b/partitura/io/exportmidi.py @@ -403,12 +403,20 @@ def to_ppq(t): ) ) else: # just add the time signature that are explicit in partitura - for ts in part.iter_all(score.TimeSignature): - meta_events[part][to_ppq(ts.start.t)].append( - MetaMessage( + for i, ts in enumerate(part.iter_all(score.TimeSignature)): + if anacrusis_behavior == "pad_bar" and i == 0: + # shift the first time signature to 0 so MIDI players can pick up the correct measure position + meta_events[part][0].append( + MetaMessage( "time_signature", numerator=ts.beats, denominator=ts.beat_type + ) + ) + else: #follow the position in the partitura part + meta_events[part][to_ppq(ts.start.t)].append( + MetaMessage( + "time_signature", numerator=ts.beats, denominator=ts.beat_type + ) ) - ) for ks in part.iter_all(score.KeySignature): meta_events[part][to_ppq(ks.start.t)].append( diff --git a/partitura/utils/music.py b/partitura/utils/music.py index aff638a9..4725a666 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3456,7 +3456,7 @@ def tokenize(score_data : ScoreLike, tokenizer : miditok.midi_tokenizer.MidiToke raise ImportError("Miditok must be installed for this function to work") with TemporaryDirectory() as tmpdir: temp_midi_path = os.path.join(tmpdir, "temp_midi.mid") - partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, anacrusis_behavior="time_sig_change", part_voice_assign_mode = 4, minimum_ppq = 480 ) + partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, anacrusis_behavior="pad_bar", part_voice_assign_mode = 4, minimum_ppq = 480 ) midi = miditoolkit.MidiFile(temp_midi_path) tokens = tokenizer(midi) return tokens diff --git a/tests/test_midi_export.py b/tests/test_midi_export.py index bbc11c03..9efe92b8 100644 --- a/tests/test_midi_export.py +++ b/tests/test_midi_export.py @@ -508,7 +508,13 @@ def test_pad_bar(self): mid_pt = mido.MidiFile(temp_midi_path) ts_messages = [m for m in list(mid_pt.tracks[0]) if isinstance(m,mido.MetaMessage) and m.type == "time_signature"] self.assertTrue(len(ts_messages)==1) - note_on_messages = [m for m in list(mid_pt.tracks[0]) if isinstance(m,mido.Message) and m.type == "note_on"] - # TODO: check why the first note is still on a downbeat + # check the position of the first note_on message + all_messages = list(mid_pt.tracks[0]) + cumulative_position = 0 + index = 0 + while not (isinstance(all_messages[index],mido.Message) and all_messages[index].type == "note_on"): + cumulative_position += all_messages[index].time + index+=1 + self.assertTrue(cumulative_position==3) From cd1f08a8d3a00ee10974a2bf5b559386be9b0b76 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 7 Jun 2023 15:06:36 +0200 Subject: [PATCH 096/122] fix for empty note features, i.e. issue #217 --- partitura/musicanalysis/note_features.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index e5561aec..ff2e7d80 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -560,7 +560,8 @@ def to_name(d): bf += feature_function_activation(d)(onsets) if not force_size: - names = [None] * len(feature_by_name) + M = len(feature_by_name) if len(feature_by_name) > 0 else 1 + names = [None] * M W = np.zeros((len(onsets), len(names))) for name, (j, bf) in feature_by_name.items(): if force_size: @@ -645,7 +646,8 @@ def to_name(d): if not force_size: - names = [None] * len(feature_by_name) + M = len(feature_by_name) if len(feature_by_name) > 0 else 1 + names = [None] * M W = np.zeros((len(onsets), len(names))) for name, (j, bf) in feature_by_name.items(): if force_size: @@ -693,8 +695,9 @@ def to_name(d): W = np.zeros((len(onsets), len(constant_names))) names = constant_names else: - W = np.zeros((len(onsets), len(feature_by_name))) - names = [None] * len(feature_by_name) + M = len(feature_by_name) if len(feature_by_name) > 0 else 1 + W = np.zeros((len(onsets), M)) + names = [None] * M for name, (j, bf) in feature_by_name.items(): if force_size: @@ -859,7 +862,7 @@ def articulation_feature(na, part, **kwargs): if force_size: M = len(names) else: - M = len(feature_by_name) + M = len(feature_by_name) if len(feature_by_name) > 0 else 1 names = [None] * M W = np.zeros((N, M)) @@ -919,7 +922,7 @@ def ornament_feature(na, part, **kwargs): if fix_size: M = len(names) else: - M = len(feature_by_name) + M = len(feature_by_name) if len(feature_by_name) > 0 else 1 names = [None] * M W = np.zeros((N, M)) From ab536404a964223dc67bca3db675a6b1d7bf1703 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 7 Jun 2023 15:32:08 +0200 Subject: [PATCH 097/122] new tests for tokenizer --- partitura/utils/music.py | 9 ++++-- tests/data/midi/test_anacrusis.mid | Bin 222 -> 135 bytes tests/test_utils.py | 50 ++++++++++++++++++++++++----- 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 4725a666..16ca26ef 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -19,10 +19,13 @@ try: import miditok + from miditok.midi_tokenizer import MIDITokenizer import miditoolkit except ImportError: miditok = None miditoolkit = None + class MIDITokenizer(object): + pass from partitura.utils.misc import deprecated_alias @@ -3451,9 +3454,9 @@ def slice_ppart_by_time( return ppart_slice -def tokenize(score_data : ScoreLike, tokenizer : miditok.midi_tokenizer.MidiTokenizer if miditok is not None else None): - if miditok is None: - raise ImportError("Miditok must be installed for this function to work") +def tokenize(score_data : ScoreLike, tokenizer : MIDITokenizer): + if miditok is None or miditoolkit is None: + raise ImportError("Miditok and miditoolkit must be installed for this function to work") with TemporaryDirectory() as tmpdir: temp_midi_path = os.path.join(tmpdir, "temp_midi.mid") partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, anacrusis_behavior="pad_bar", part_voice_assign_mode = 4, minimum_ppq = 480 ) diff --git a/tests/data/midi/test_anacrusis.mid b/tests/data/midi/test_anacrusis.mid index 55c830ca8f05762584a7fb0b8ea0d8d1572f75a2..9a0522d1db4f4c47f45e7dca49398560636495d6 100644 GIT binary patch literal 135 zcmeYb$w*;fU|?flWME=G;2Tnu4df{?{AcFQ%}mLRPpv4(FDha9AHl-HB*DS(KaiPy zp#sByeFmsXcc4m+pwz_R%Dj?{X$})?>f0R}Y#10O*wxQtXs`n^9P0ZS8XSNON05{w Skl_qsI0G53AT_QGAmaeLR3_E{ literal 222 zcmeYb$w*;fU|?flWME``;2Tnu4dk6*_|ME5keQg5&+tEjg^@{ugW-Q96HwxRAT#?y z1%?fk3=9Vt7&fq{FmTy3L^CkN0;Log85kzm1T?$bFh~M*v4C_ Date: Wed, 7 Jun 2023 15:42:16 +0200 Subject: [PATCH 098/122] documentation --- partitura/utils/music.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 16ca26ef..0085dbf0 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3454,12 +3454,33 @@ def slice_ppart_by_time( return ppart_slice -def tokenize(score_data : ScoreLike, tokenizer : MIDITokenizer): +def tokenize(score_data : ScoreLike, tokenizer : MIDITokenizer, incomplete_bar_behaviour : str = "pad_bar"): + """ + Tokenize a score using a tokenizer from miditok. + Parameters + ---------- + score_data : Score, list, Part, or PartGroup + The musical score to be saved. A :class:`partitura.score.Score` object, + a :class:`partitura.score.Part`, a :class:`partitura.score.PartGroup` or + a list of these. + tokenizer : MIDITokenizer + A tokenizer from miditok. + incomplete_bar_behaviour : str + How to handle incomplete bars at the beginning (pickup measures) and + during the score. Can be one of 'pad_bar', 'shift', or 'time_sig_change'. + See :func:`partitura.io.exportmidi.save_score_midi` for details. + Defaults to 'pad_bar'. + Returns + ------- + ppart_slice : `Tokens` object + Tokens as produced by the miditok library. + """ + if miditok is None or miditoolkit is None: raise ImportError("Miditok and miditoolkit must be installed for this function to work") with TemporaryDirectory() as tmpdir: temp_midi_path = os.path.join(tmpdir, "temp_midi.mid") - partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, anacrusis_behavior="pad_bar", part_voice_assign_mode = 4, minimum_ppq = 480 ) + partitura.io.exportmidi.save_score_midi(score_data, out = temp_midi_path, anacrusis_behavior=incomplete_bar_behaviour, part_voice_assign_mode = 4, minimum_ppq = 480 ) midi = miditoolkit.MidiFile(temp_midi_path) tokens = tokenizer(midi) return tokens From 07f0a600ea048e32b3b0c46b7e4e2b37b07aa5ee Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Wed, 7 Jun 2023 15:46:39 +0200 Subject: [PATCH 099/122] Update partitura_unittests.yml Added optional dependencies for tokenization on unit test workflow --- .github/workflows/partitura_unittests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index d2b4388a..3e2cfe72 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -25,10 +25,12 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . - - name: Instal Optional dependencies + - name: Install Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 - pip install torch + - name: Install Tokenization dependencies (optional) + run: | + pip install miditok==2.0.6 tokenizers==0.13.3 - name: Run Tests run: | pip install coverage From 0baac7ba58c50f32dea14c6455a13e9814c34d3e Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Wed, 7 Jun 2023 16:06:58 +0200 Subject: [PATCH 100/122] Update partitura_unittests.yml Some minor editing on the work flow --- .github/workflows/partitura_unittests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index 3e2cfe72..c9262aa2 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -27,9 +27,9 @@ jobs: pip install . - name: Install Optional dependencies run: | + # Music21 and musescore import dependencies pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 - - name: Install Tokenization dependencies (optional) - run: | + # Tokenization export dependencies. pip install miditok==2.0.6 tokenizers==0.13.3 - name: Run Tests run: | From 535f0f1de525f331a7775613dcb0107696715f07 Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Wed, 7 Jun 2023 16:08:54 +0200 Subject: [PATCH 101/122] Update partitura_unittests.yml --- .github/workflows/partitura_unittests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index c9262aa2..8f3efe44 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -27,9 +27,7 @@ jobs: pip install . - name: Install Optional dependencies run: | - # Music21 and musescore import dependencies - pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 - # Tokenization export dependencies. + pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 pip install miditok==2.0.6 tokenizers==0.13.3 - name: Run Tests run: | From 8d4bfb30a8299ed67335c148859dde5ee645e06e Mon Sep 17 00:00:00 2001 From: sildater Date: Wed, 7 Jun 2023 16:37:48 +0200 Subject: [PATCH 102/122] fix asserts and sorting --- .../musicanalysis/note_array_to_score.py | 32 +++++++++++++++---- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 9cdf1581..cdf56d87 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -316,6 +316,20 @@ def note_array_to_score( case2_ex = ["onset_beat", "duration_beat"] # case3 = ["onset_div", "duration_div", "onset_beat", "duration_beat", "pitch"] + if not all([x in dtypes for x in case1]) or all([x in dtypes for x in case2]): + raise ValueError("not all necessary note array fields are available") + + # sort the array + onset_time = "onset_div" + if all([x not in dtypes for x in case1_ex]): + onset_time = "onset_beat" + + pitch_sort_idx = np.argsort(note_array["pitch"]) + note_array = note_array[pitch_sort_idx] + onset_sort_idx = np.argsort(note_array[onset_time], kind="mergesort") + note_array = note_array[onset_sort_idx] + + # case 1, estimate divs if all([x in dtypes for x in case1] and [x not in dtypes for x in case1_ex]): # estimate onset_divs and duration_divs, assumes all beat times as quarters @@ -366,7 +380,16 @@ def note_array_to_score( else: divs = int(note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) - + # Test Note array for negative durations + if not np.all(note_array["duration_div"] >= 0): + raise ValueError("Note array contains negative durations.") + if not np.all(note_array["duration_beat"] >= 0): + raise ValueError("Note array contains negative durations.") + + # Test for negative divs + if not np.all(note_array["onset_div"] >= 0): + raise ValueError("Negative divs found in note_array.") + # handle time signatures if all([x in dtypes for x in ts_case]): time_sigs = [[0, note_array[0]["ts_beats"], note_array[0]["ts_beat_type"]]] @@ -395,12 +418,7 @@ def note_array_to_score( # make sure there is a time signature from the beginning global_time_sigs[0, 0] = 0 - # Test Note array for negative durations - assert np.all(note_array["duration_div"] >= 0), "Note array contains negative durations." - assert np.all(note_array["duration_beat"] >= 0), "Note array contains negative durations." - - # Test for negative divs - assert np.all(note_array["onset_div"] >= 0), "Negative divs found in note_array." + # Note id creation or re-assignment if "id" not in dtypes: From 1fbab4db5d24cc8f281ee9bf45184d13eb932a72 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 7 Jun 2023 16:47:36 +0200 Subject: [PATCH 103/122] correction of indentation suggestion and looping over endpoints. --- partitura/io/importmatch.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index 30ad5ff5..ee1bf443 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -677,21 +677,22 @@ def part_from_matchfile( part.add(part_note, onset_divs, offset_divs) - # Check if the note is tied and if so, add the tie information - if is_tied: - found = False - for el in part.iter_all(end=offset_divs): - if isinstance(el, score.Note): - condition = el.step == note_attributes["step"] and el.octave == note_attributes["octave"] and el.alter == note_attributes["alter"] - if condition: - el.tie_next = part_note - part_note.tie_prev = el - found = True - break - if not found: - warnings.warn( - "Tie information found, but no previous note found to tie to for note {}.".format(part_note.id) - ) + # Check if the note is tied and if so, add the tie information + if is_tied: + found = False + # iterate over all notes in the Timeline that end at the starting point. + for el in part_note.start.iter_ending(score.Note): + if isinstance(el, score.Note): + condition = el.step == note_attributes["step"] and el.octave == note_attributes["octave"] and el.alter == note_attributes["alter"] + if condition: + el.tie_next = part_note + part_note.tie_prev = el + found = True + break + if not found: + warnings.warn( + "Tie information found, but no previous note found to tie to for note {}.".format(part_note.id) + ) # add time signatures for (ts_beat_time, ts_bar, tsg) in ts: From 4a5a88ac576f091579692374e4f49d998787ce47 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 7 Jun 2023 17:05:32 +0200 Subject: [PATCH 104/122] solves #212 --- partitura/musicanalysis/note_features.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index ff2e7d80..917d51f4 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -491,7 +491,7 @@ def grace_feature(na, part, **kwargs): grace = grace_notes[i] n_grace = np.count_nonzero(grace_notes["onset_beat"] == grace["onset_beat"]) W[index, 1] = n_grace - W[index, 2] = n_grace - sum(1 for _ in notes[grace["id"]].iter_grace_seq()) + 1 + W[index, 2] = n_grace - sum(1 for _ in notes[grace["id"]].iter_grace_seq()) + 1 if grace["id"] not in (None, 'None', "") else 0 return W, feature_names @@ -941,11 +941,10 @@ def staff_feature(na, part, **kwargs): """Staff feature""" names = ["staff"] notes = {n.id: n.staff for n in part.notes_tied} - N = len(notes) + N = len(na) W = np.zeros((N, 1)) for i, n in enumerate(na): - W[i, 0] = notes[n["id"]] - + W[i, 0] = notes[n["id"]] if n["id"] not in (None, "None", "") else 0 return W, names From c060ae5edf5bd3e9e85784d3628f299df2ce8579 Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Wed, 7 Jun 2023 17:22:08 +0200 Subject: [PATCH 105/122] Fixing minor parenthesis missing bug. --- partitura/musicanalysis/note_array_to_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index cdf56d87..9e4fee3f 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -316,7 +316,7 @@ def note_array_to_score( case2_ex = ["onset_beat", "duration_beat"] # case3 = ["onset_div", "duration_div", "onset_beat", "duration_beat", "pitch"] - if not all([x in dtypes for x in case1]) or all([x in dtypes for x in case2]): + if not (all([x in dtypes for x in case1]) or all([x in dtypes for x in case2])): raise ValueError("not all necessary note array fields are available") # sort the array From e6f7124610b7886ae13378b60d2d6ccd32401fa1 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 11:50:16 +0200 Subject: [PATCH 106/122] lexsort, reference, test file names, match file version --- partitura/musicanalysis/note_features.py | 8 +- .../musicanalysis/performance_features.py | 21 +- tests/data/match/Chopin_op10_no3_p01.match | 916 +++++++++--------- ...ssions.py => test_performance_features.py} | 0 4 files changed, 475 insertions(+), 470 deletions(-) rename tests/{test_performance_expressions.py => test_performance_features.py} (100%) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index f2411ed3..bf2bb2bc 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -387,10 +387,10 @@ def compute_note_array( feature_data_struct = make_note_feats(part, feature_functions, add_idx=True) note_array_joined = np.lib.recfunctions.join_by("id", na, feature_data_struct) note_array = note_array_joined.data - pitch_sort_idx = np.argsort(note_array["pitch"]) - note_array = note_array[pitch_sort_idx] - onset_sort_idx = np.argsort(note_array["onset_div"], kind="mergesort") - note_array = note_array[onset_sort_idx] + sort_idx = np.lexsort((note_array["duration_div"], + note_array["pitch"], + note_array["onset_div"])) + note_array = note_array[sort_idx] else: note_array = na return note_array diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index b4882bfb..178cbced 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -117,10 +117,11 @@ def compute_performance_features(score: ScoreLike, performance_features = rfn.merge_arrays(acc, flatten=True, usemask=False) full_performance_features = rfn.join_by("id", performance_features, m_score) full_performance_features = full_performance_features.data - pitch_sort_idx = np.argsort(full_performance_features["pitch"]) - full_performance_features = full_performance_features[pitch_sort_idx] - onset_sort_idx = np.argsort(full_performance_features["onset"], kind="mergesort") - full_performance_features = full_performance_features[onset_sort_idx] + + sort_idx = np.lexsort((full_performance_features["duration"], + full_performance_features["pitch"], + full_performance_features["onset"])) + full_performance_features = full_performance_features[sort_idx] return full_performance_features @@ -154,8 +155,9 @@ def compute_matched_score(score: ScoreLike, return_u_onset_idx=True, tempo_smooth="average" ) - m_score = rfn.append_fields(m_score, "beat_period", time_params['beat_period'], "f4", usemask=False) - m_score = rfn.append_fields(m_score, "id", snote_ids, "U256", usemask=False) + m_score = rfn.append_fields(m_score, ["beat_period", "id"], + [time_params['beat_period'], snote_ids], + ["f4", "U256"], usemask=False,) return m_score, unique_onset_idxs, snote_ids @@ -307,8 +309,11 @@ def articulation_feature(m_score : np.ndarray, Compute the articulation attributes (key overlap ratio) from the alignment. Key overlap ratio is the ratio between key overlap time (KOT) and IOI, result in a value between (-1, inf) -1 is the dummy value. For normalization purposes we empirically cap the maximum to 5. - B.Repp: Acoustics, Perception, and Production of Legato Articulation on a Digital Piano - + + References + ---------- + .. [1] B.Repp: Acoustics, Perception, and Production of Legato Articulation on a Digital Piano + Parameters ---------- m_score : list diff --git a/tests/data/match/Chopin_op10_no3_p01.match b/tests/data/match/Chopin_op10_no3_p01.match index d2f239c7..258af805 100644 --- a/tests/data/match/Chopin_op10_no3_p01.match +++ b/tests/data/match/Chopin_op10_no3_p01.match @@ -1,467 +1,467 @@ -info(matchFileVersion,4.0). +info(matchFileVersion,1.0.0). info(piece,Chopin_op10_no3). -info(scoreFileName,Chopin_op10_no3_p01.match). +info(scoreFileName,Chopin_op10_no3.musicxml). info(midiFileName,Chopin_op10_no3_p01.mid). info(composer,Frèdéryk Chopin). info(performer,Pianist 01). info(midiClockUnits,4000). info(midiClockRate,500000). -info(keySignature,[E Maj]). -info(timeSignature,[2/4]). -snote(n1,[b,n],3,0:2,1/8,1/8,-0.5,0.0,[1])-note(0,[b,n],3,39940,42140,46960,44). -snote(n2,[e,n],4,1:1,0,1/8,0.0,0.5,[1])-note(2,[e,n],4,45630,55280,65120,54). -snote(n3,[g,#],3,1:1,0,1/16,0.0,0.25,[3])-note(3,[g,#],3,46210,51830,65120,26). -snote(n4,[e,n],2,1:1,0,1/4,0.0,1.0,[7])-note(1,[e,n],2,45620,60390,65120,22). -snote(n5,[b,n],3,1:1,1/16,1/16,0.25,0.5,[3])-note(5,[b,n],3,51570,56570,65120,37). -snote(n6,[b,n],2,1:1,1/16,1/8,0.25,0.75,[4])-note(4,[b,n],2,51460,55780,65120,20). -snote(n7,[d,#],4,1:1,1/8,1/16,0.5,0.75,[1])-note(6,[d,#],4,55780,60210,65120,52). -snote(n8,[g,#],3,1:1,1/8,1/16,0.5,0.75,[3])-note(7,[g,#],3,56100,58580,65120,32). -snote(n9,[e,n],4,1:1,3/16,1/16,0.75,1.0,[1])-note(8,[e,n],4,59810,64900,65120,59). -snote(n10,[b,n],3,1:1,3/16,1/16,0.75,1.0,[3])-note(9,[b,n],3,60020,64520,65120,41). -snote(n11,[b,n],2,1:1,3/16,1/16,0.75,1.0,[4])-note(10,[b,n],2,60150,64600,65120,26). -snote(n12,[f,#],4,1:2,0,5/16,1.0,2.25,[1])-note(11,[f,#],4,64270,83250,97920,58). -snote(n13,[d,#],4,1:2,0,3/8,1.0,2.5,[2])-note(13,[d,#],4,64550,85870,97920,41). -snote(n14,[a,n],3,1:2,0,1/16,1.0,1.25,[3])-note(14,[a,n],3,64720,69720,69720,32). -snote(n15,[b,n],1,1:2,0,1/4,1.0,2.0,[7])-note(12,[b,n],1,64550,76630,80880,30). -snote(n16,[b,n],3,1:2,1/16,1/16,1.25,1.5,[3])-note(16,[b,n],3,69850,74250,80880,36). -snote(n17,[b,n],2,1:2,1/16,1/8,1.25,1.75,[4])-note(15,[b,n],2,69630,73620,80880,31). -snote(n18,[a,n],3,1:2,1/8,1/16,1.5,1.75,[3])-note(17,[a,n],3,73660,77370,80880,40). -snote(n19,[b,n],3,1:2,3/16,1/16,1.75,2.0,[3])-note(19,[b,n],3,77360,81790,81790,44). -snote(n20,[b,n],2,1:2,3/16,1/16,1.75,2.0,[4])-note(18,[b,n],2,77330,81190,81190,30). -snote(n21,[a,n],3,2:1,0,1/16,2.0,2.25,[3])-note(20,[a,n],3,81100,84910,97920,39). -snote(n22,[b,n],1,2:1,0,1/4,2.0,3.0,[7])-note(21,[b,n],1,81120,94120,97920,30). -snote(n23,[g,#],4,2:1,1/16,1/16,2.25,2.5,[1])-note(22,[g,#],4,85040,87070,97920,57). -snote(n24,[b,n],3,2:1,1/16,1/16,2.25,2.5,[3])-note(23,[b,n],3,85070,86590,97920,44). -snote(n25,[b,n],2,2:1,1/16,1/8,2.25,2.75,[4])-note(24,[b,n],2,85220,88030,97920,31). -snote(n26,[g,#],4,2:1,1/8,1/16,2.5,2.75,[1])-note(25,[g,#],4,88770,91740,97920,63). -snote(n27,[d,#],4,2:1,1/8,1/8,2.5,3.0,[2])-note(26,[d,#],4,88930,93360,97920,48). -snote(n28,[a,n],3,2:1,1/8,1/16,2.5,2.75,[3])-note(27,[a,n],3,89400,91580,97920,11). -snote(n29,[f,#],4,2:1,3/16,1/16,2.75,3.0,[1])-note(28,[f,#],4,92480,94170,97920,65). -snote(n30,[b,n],3,2:1,3/16,1/16,2.75,3.0,[3])-note(29,[b,n],3,92730,93270,97920,45). -snote(n31,[b,n],2,2:1,3/16,1/16,2.75,3.0,[4])-note(30,[b,n],2,92780,94760,97920,31). -snote(n32,[g,#],4,2:2,0,5/16,3.0,4.25,[1])-note(31,[g,#],4,97270,115460,130400,61). -snote(n33,[e,n],4,2:2,0,1/4,3.0,4.0,[2])-note(33,[e,n],4,97500,114040,114040,48). -snote(n34,[g,#],3,2:2,0,1/16,3.0,3.25,[3])-note(34,[g,#],3,97580,102500,113440,33). -snote(n35,[e,n],2,2:2,0,1/4,3.0,4.0,[7])-note(32,[e,n],2,97450,109830,113440,29). -snote(n36,[b,n],3,2:2,1/16,1/16,3.25,3.5,[3])-note(36,[b,n],3,102570,106790,113440,38). -snote(n37,[b,n],2,2:2,1/16,1/8,3.25,3.75,[4])-note(35,[b,n],2,102560,106020,113440,27). -snote(n38,[g,#],3,2:2,1/8,1/16,3.5,3.75,[3])-note(37,[g,#],3,106450,109590,113440,36). -snote(n39,[b,n],3,2:2,3/16,1/16,3.75,4.0,[3])-note(39,[b,n],3,109910,113780,113780,43). -snote(n40,[b,n],2,2:2,3/16,1/16,3.75,4.0,[4])-note(38,[b,n],2,109860,113630,113630,25). -snote(n41,[g,#],3,3:1,0,1/16,4.0,4.25,[3])-note(41,[g,#],3,113700,115950,130400,41). -snote(n42,[e,n],2,3:1,0,1/4,4.0,5.0,[7])-note(40,[e,n],2,113580,125350,130400,27). -snote(n43,[a,n],4,3:1,1/16,1/16,4.25,4.5,[1])-note(42,[a,n],4,117340,119200,130400,57). -snote(n44,[e,n],4,3:1,1/16,1/16,4.25,4.5,[3])-note(44,[e,n],4,117670,118780,130400,42). -snote(n45,[b,n],2,3:1,1/16,1/8,4.25,4.75,[4])-note(43,[b,n],2,117510,119720,130400,33). -snote(n46,[a,n],4,3:1,1/8,1/16,4.5,4.75,[1])-note(45,[a,n],4,121000,124990,130400,68). -snote(n47,[b,n],3,3:1,1/8,1/16,4.5,4.75,[3])-note(46,[b,n],3,121410,123650,130400,44). -snote(n48,[g,#],4,3:1,3/16,1/16,4.75,5.0,[1])-note(47,[g,#],4,124670,126260,130400,72). -snote(n49,[e,n],4,3:1,3/16,1/16,4.75,5.0,[3])-note(48,[e,n],4,124960,125340,130400,45). -snote(n50,[b,n],2,3:1,3/16,1/16,4.75,5.0,[4])-note(49,[b,n],2,124970,127130,130400,34). -snote(n51,[c,#],5,3:2,0,3/16,5.0,5.75,[1])-note(50,[c,#],5,129250,135540,146720,71). -snote(n52,[d,#],4,3:2,0,1/16,5.0,5.25,[3])-note(51,[d,#],4,129490,135340,146720,51). -snote(n53,[b,n],1,3:2,0,1/4,5.0,6.0,[7])-note(52,[b,n],1,129550,142060,146720,36). -snote(n54,[a,n],4,3:2,1/16,1/16,5.25,5.5,[3])-note(54,[a,n],4,134040,135850,146720,56). -snote(n55,[b,n],2,3:2,1/16,1/8,5.25,5.75,[4])-note(53,[b,n],2,133890,136630,146720,34). -snote(n56,[b,n],3,3:2,1/8,1/16,5.5,5.75,[3])-note(55,[b,n],3,137780,140480,146720,49). -snote(n57,[b,n],4,3:2,3/16,1/16,5.75,6.0,[1])-note(56,[b,n],4,141230,142200,146720,60). -snote(n58,[d,#],4,3:2,3/16,1/16,5.75,6.0,[3])-note(57,[d,#],4,141470,142220,146720,44). -snote(n59,[b,n],2,3:2,3/16,1/16,5.75,6.0,[4])-note(58,[b,n],2,141540,143270,146720,34). -snote(n60,[a,n],4,4:1,0,1/16,6.0,6.25,[1])-note(59,[a,n],4,145510,150400,150400,59). -snote(n61,[b,n],3,4:1,0,1/16,6.0,6.25,[3])-note(61,[b,n],3,145780,149340,149340,42). -snote(n62,[e,n],2,4:1,0,1/4,6.0,7.0,[7])-note(60,[e,n],2,145610,158320,161600,30). -snote(n63,[g,#],4,4:1,1/16,1/16,6.25,6.5,[1])-note(62,[g,#],4,149580,151760,156880,52). -snote(n64,[e,n],4,4:1,1/16,1/16,6.25,6.5,[3])-note(63,[e,n],4,149730,150890,156880,36). -snote(n65,[b,n],2,4:1,1/16,1/8,6.25,6.75,[4])-note(64,[b,n],2,149840,152800,156880,11). -snote(n66,[d,#],4,4:1,1/8,1/16,6.5,6.75,[1])-note(65,[d,#],4,153100,156670,156880,53). -snote(n67,[g,#],3,4:1,1/8,1/16,6.5,6.75,[3])-note(66,[g,#],3,153490,155610,156880,39). -snote(n68,[e,n],4,4:1,3/16,1/16,6.75,7.0,[1])-note(67,[e,n],4,156810,161700,161700,55). -snote(n69,[b,n],3,4:1,3/16,1/16,6.75,7.0,[3])-note(68,[b,n],3,156950,158350,161600,39). -snote(n70,[b,n],2,4:1,3/16,1/16,6.75,7.0,[4])-note(69,[b,n],2,157040,160560,161600,29). -snote(n71,[f,#],4,4:2,0,5/16,7.0,8.25,[1])-note(70,[f,#],4,161500,179640,179640,54). -snote(n72,[c,#],4,4:2,0,1/16,7.0,7.25,[2])-note(72,[c,#],4,161960,167190,167280,34). -snote(n73,[a,n],3,4:2,0,1/16,7.0,7.25,[3])-note(73,[a,n],3,162010,164240,167280,27). -snote(n74,[b,n],1,4:2,0,1/4,7.0,8.0,[7])-note(71,[b,n],1,161730,175760,178080,25). -snote(n75,[d,#],4,4:2,1/16,1/16,7.25,7.5,[2])-note(74,[d,#],4,167060,170840,174560,41). -snote(n76,[b,n],3,4:2,1/16,1/16,7.25,7.5,[3])-note(76,[b,n],3,167650,169160,169160,17). -snote(n77,[b,n],2,4:2,1/16,1/8,7.25,7.75,[4])-note(75,[b,n],2,167200,171160,174560,29). -snote(n78,[c,#],4,4:2,1/8,1/16,7.5,7.75,[2])-note(77,[c,#],4,170980,174810,174810,42). -snote(n79,[a,n],3,4:2,1/8,1/16,7.5,7.75,[3])-note(78,[a,n],3,171320,171940,174560,30). -snote(n80,[d,#],4,4:2,3/16,1/16,7.75,8.0,[2])-note(80,[d,#],4,174670,178160,178160,41). -snote(n81,[b,n],3,4:2,3/16,1/16,7.75,8.0,[3])-note(81,[b,n],3,174920,175730,178080,26). -snote(n82,[b,n],2,4:2,3/16,1/16,7.75,8.0,[4])-note(79,[b,n],2,174620,178460,178460,34). -snote(n83,[c,#],4,5:1,0,1/16,8.0,8.25,[2])-note(83,[c,#],4,178350,182380,182720,38). -snote(n84,[a,n],3,5:1,0,1/16,8.0,8.25,[3])-note(84,[a,n],3,178530,179500,179500,31). -snote(n85,[b,n],1,5:1,0,1/4,8.0,9.0,[7])-note(82,[b,n],1,178340,191130,194800,31). -snote(n86,[g,#],4,5:1,1/16,1/16,8.25,8.5,[1])-note(85,[g,#],4,182560,185450,194800,55). -snote(n87,[d,#],4,5:1,1/16,1/16,8.25,8.5,[2])-note(86,[d,#],4,182660,185840,194800,40). -snote(n88,[b,n],3,5:1,1/16,1/16,8.25,8.5,[3])-note(88,[b,n],3,182830,184310,184310,34). -snote(n89,[b,n],2,5:1,1/16,1/8,8.25,8.75,[4])-note(87,[b,n],2,182770,186610,194800,31). -snote(n90,[g,#],4,5:1,1/8,1/16,8.5,8.75,[1])-note(89,[g,#],4,186310,189250,194800,60). -snote(n91,[c,#],4,5:1,1/8,1/16,8.5,8.75,[2])-note(90,[c,#],4,186340,188090,194800,46). -snote(n92,[a,n],3,5:1,1/8,1/16,8.5,8.75,[3])-note(91,[a,n],3,186490,187120,194800,38). -snote(n93,[f,#],4,5:1,3/16,1/16,8.75,9.0,[1])-note(92,[f,#],4,190020,191400,194800,57). -snote(n94,[d,#],4,5:1,3/16,1/16,8.75,9.0,[2])-note(94,[d,#],4,190240,191150,194800,45). -snote(n95,[b,n],3,5:1,3/16,1/16,8.75,9.0,[3])-note(93,[b,n],3,190200,190650,194800,42). -snote(n96,[b,n],2,5:1,3/16,1/16,8.75,9.0,[4])-note(95,[b,n],2,190300,194130,194800,37). -snote(n97,[e,n],4,5:2,0,1/4,9.0,10.0,[1])-note(96,[e,n],4,194370,207290,213120,57). -snote(n98,[g,#],3,5:2,0,1/16,9.0,9.25,[3])-note(98,[g,#],3,194890,198940,198940,25). -snote(n99,[e,n],2,5:2,0,1/4,9.0,10.0,[7])-note(97,[e,n],2,194550,205150,213120,28). -snote(n100,[b,n],3,5:2,1/16,1/16,9.25,9.5,[3])-note(100,[b,n],3,198750,202960,213120,37). -snote(n101,[b,n],2,5:2,1/16,1/8,9.25,9.75,[4])-note(99,[b,n],2,198730,201830,213120,23). -snote(n102,[g,#],3,5:2,1/8,1/16,9.5,9.75,[3])-note(101,[g,#],3,202330,204330,213120,36). -snote(n103,[b,n],3,5:2,3/16,1/16,9.75,10.0,[3])-note(102,[b,n],3,206520,207010,213120,28). -snote(n104,[b,n],2,5:2,3/16,1/16,9.75,10.0,[4])-note(103,[b,n],2,206530,207340,213120,26). -snote(n105,[g,#],4,6:1,0,1/16,10.0,10.25,[1])-note(104,[g,#],4,211380,215630,215630,46). -snote(n106,[d,n],4,6:1,0,1/16,10.0,10.25,[3])-note(106,[d,n],4,211610,215430,215430,32). -snote(n107,[e,n],2,6:1,0,1/4,10.0,11.0,[7])-note(105,[e,n],2,211410,224770,226240,28). -snote(n108,[a,n],4,6:1,1/16,1/16,10.25,10.5,[1])-note(107,[a,n],4,215240,218910,226240,50). -snote(n109,[e,n],4,6:1,1/16,1/16,10.25,10.5,[3])-note(109,[e,n],4,215540,218500,226240,40). -snote(n110,[e,n],3,6:1,1/16,1/8,10.25,10.75,[4])-note(108,[e,n],3,215300,218960,226240,35). -snote(n111,[f,#],4,6:1,1/8,1/16,10.5,10.75,[1])-note(110,[f,#],4,218480,222550,226240,55). -snote(n112,[d,n],4,6:1,1/8,1/16,10.5,10.75,[3])-note(111,[d,n],4,218800,220920,226240,40). -snote(n113,[g,#],4,6:1,3/16,1/16,10.75,11.0,[1])-note(112,[g,#],4,221900,223760,226240,62). -snote(n114,[e,n],4,6:1,3/16,1/16,10.75,11.0,[3])-note(114,[e,n],4,222250,222720,226240,31). -snote(n115,[e,n],3,6:1,3/16,1/16,10.75,11.0,[4])-note(113,[e,n],3,221960,224830,226240,39). -snote(n116,[a,n],4,6:2,0,1/16,11.0,11.25,[1])-note(115,[a,n],4,225330,229050,229050,68). -snote(n117,[c,#],4,6:2,0,1/16,11.0,11.25,[3])-note(116,[c,#],4,225620,229230,229230,46). -snote(n118,[a,n],2,6:2,0,1/4,11.0,12.0,[7])-note(117,[a,n],2,225640,235050,239600,34). -snote(n119,[b,n],4,6:2,1/16,1/16,11.25,11.5,[1])-note(118,[b,n],4,228650,231870,239600,70). -snote(n120,[e,n],4,6:2,1/16,1/16,11.25,11.5,[3])-note(120,[e,n],4,228850,232150,239600,57). -snote(n121,[e,n],3,6:2,1/16,1/8,11.25,11.75,[4])-note(119,[e,n],3,228720,231850,239600,40). -snote(n122,[g,#],4,6:2,1/8,1/16,11.5,11.75,[1])-note(121,[g,#],4,231860,235880,239600,68). -snote(n123,[c,#],4,6:2,1/8,1/16,11.5,11.75,[3])-note(122,[c,#],4,231950,235040,239600,61). -snote(n124,[a,n],4,6:2,3/16,1/16,11.75,12.0,[1])-note(123,[a,n],4,235160,239480,239600,72). -snote(n125,[e,n],4,6:2,3/16,1/16,11.75,12.0,[3])-note(125,[e,n],4,235380,237030,239600,47). -snote(n126,[e,n],3,6:2,3/16,1/16,11.75,12.0,[4])-note(124,[e,n],3,235300,239290,239600,40). -snote(n127,[c,#],5,7:1,0,1/8,12.0,12.5,[1])-note(127,[c,#],5,239070,246000,254080,71). -snote(n128,[c,#],4,7:1,0,1/16,12.0,12.25,[3])-note(128,[c,#],4,239370,244770,244770,45). -snote(n129,[a,n],2,7:1,0,1/4,12.0,13.0,[7])-note(126,[a,n],2,238980,253980,254080,38). -snote(n130,[e,n],4,7:1,1/16,1/16,12.25,12.5,[3])-note(130,[e,n],4,243620,247380,254080,48). -snote(n131,[f,#],3,7:1,1/16,1/8,12.25,12.75,[4])-note(129,[f,#],3,243410,247960,254080,55). -snote(n132,[f,#],4,7:1,1/8,1/4,12.5,13.5,[1])-note(131,[f,#],4,247010,257510,267280,64). -snote(n133,[c,#],4,7:1,1/8,1/16,12.5,12.75,[3])-note(132,[c,#],4,247540,250620,254080,34). -snote(n134,[e,n],4,7:1,3/16,1/16,12.75,13.0,[3])-note(133,[e,n],4,250460,254090,254090,53). -snote(n135,[f,#],3,7:1,3/16,1/16,12.75,13.0,[4])-note(134,[f,#],3,250630,253910,254080,39). -snote(n136,[b,n],3,7:2,0,1/16,13.0,13.25,[3])-note(135,[b,n],3,253690,257020,267280,55). -snote(n137,[b,n],2,7:2,0,1/4,13.0,14.0,[7])-note(136,[b,n],2,253760,267650,267650,48). -snote(n138,[e,n],4,7:2,1/16,1/16,13.25,13.5,[3])-note(138,[e,n],4,257030,258360,267280,59). -snote(n139,[f,#],3,7:2,1/16,1/8,13.25,13.75,[4])-note(137,[f,#],3,256860,258730,267280,40). -snote(n140,[a,#],4,7:2,1/8,0,13.5,13.5,[1,grace])-note(140,[a,#],4,259690,261790,267280,63). -snote(n141,[g,#],4,7:2,1/8,1/16,13.5,13.75,[1])-note(141,[g,#],4,260190,264370,267280,60). -snote(n142,[b,n],3,7:2,1/8,1/16,13.5,13.75,[3])-note(139,[b,n],3,259100,261360,267280,45). -snote(n143,[f,#],4,7:2,3/16,3/16,13.75,14.5,[1])-note(142,[f,#],4,263410,271400,282640,61). -snote(n144,[e,n],4,7:2,3/16,1/16,13.75,14.0,[3])-note(144,[e,n],4,263670,267660,267660,53). -snote(n145,[f,#],3,7:2,3/16,1/16,13.75,14.0,[4])-note(143,[f,#],3,263630,267490,267490,39). -snote(n146,[a,#],3,8:1,0,1/16,14.0,14.25,[3])-note(146,[a,#],3,267180,271360,282640,56). -snote(n147,[c,#],3,8:1,0,1/4,14.0,15.0,[7])-note(145,[c,#],3,267140,279190,282640,49). -snote(n148,[e,n],4,8:1,1/16,1/16,14.25,14.5,[3])-note(147,[e,n],4,270530,272240,282640,58). -snote(n149,[f,#],3,8:1,1/16,1/8,14.25,14.75,[4])-note(148,[f,#],3,270650,272610,282640,37). -snote(n150,[a,#],4,8:1,1/8,0,14.5,14.5,[1,grace])-note(150,[a,#],4,273350,275320,282640,61). -snote(n151,[g,#],4,8:1,1/8,1/16,14.5,14.75,[1])-note(151,[g,#],4,273970,278200,282640,65). -snote(n152,[a,#],3,8:1,1/8,1/16,14.5,14.75,[3])-note(149,[a,#],3,272740,277790,282640,52). -snote(n153,[f,#],4,8:1,3/16,1/16,14.75,15.0,[1])-note(152,[f,#],4,277740,280410,282640,55). -snote(n154,[e,n],4,8:1,3/16,1/16,14.75,15.0,[3])-note(153,[e,n],4,277880,278940,282640,49). -snote(n155,[f,#],3,8:1,3/16,1/16,14.75,15.0,[4])-note(154,[f,#],3,277980,279410,282640,42). -snote(n156,[b,n],4,8:2,0,1/4,15.0,16.0,[1])-note(155,[b,n],4,282610,289660,303680,53). -snote(n157,[d,#],4,8:2,0,1/4,15.0,16.0,[2])-note(156,[d,#],4,282840,287690,287920,38). -snote(n158,[a,n],3,8:2,0,1/16,15.0,15.25,[3])-note(158,[a,n],3,282950,286630,287920,32). -snote(n159,[b,n],2,8:2,0,1/4,15.0,16.0,[7])-note(157,[b,n],2,282910,293370,303680,30). -snote(n160,[b,n],3,8:2,1/16,1/16,15.25,15.5,[3])-note(159,[b,n],3,287990,290090,303680,37). -snote(n161,[f,#],3,8:2,1/16,1/8,15.25,15.75,[4])-note(160,[f,#],3,288150,292690,303680,30). -snote(n162,[a,n],3,8:2,1/8,1/16,15.5,15.75,[3])-note(161,[a,n],3,291830,292750,303680,46). -snote(n163,[b,n],3,8:2,3/16,1/16,15.75,16.0,[3])-note(162,[b,n],3,296380,297650,303680,44). -snote(n164,[f,#],3,8:2,3/16,1/16,15.75,16.0,[4])-note(163,[f,#],3,296790,297670,303680,12). -snote(n165,[g,#],4,9:1,0,1/8,16.0,16.5,[1])-note(165,[g,#],4,302750,311510,320960,45). -snote(n166,[e,n],4,9:1,0,1/8,16.0,16.5,[2])-note(167,[e,n],4,303110,310790,320960,38). -snote(n167,[g,#],3,9:1,0,1/16,16.0,16.25,[3])-note(166,[g,#],3,303030,308370,308370,26). -snote(n168,[e,n],2,9:1,0,1/4,16.0,17.0,[7])-note(164,[e,n],2,302750,315600,320960,25). -snote(n169,[b,n],3,9:1,1/16,1/16,16.25,16.5,[3])-note(169,[b,n],3,308270,312220,320960,35). -snote(n170,[b,n],2,9:1,1/16,1/8,16.25,16.75,[4])-note(168,[b,n],2,308170,311530,320960,24). -snote(n171,[d,#],4,9:1,1/8,1/16,16.5,16.75,[1])-note(170,[d,#],4,312000,315750,320960,48). -snote(n172,[g,#],3,9:1,1/8,1/16,16.5,16.75,[3])-note(171,[g,#],3,312360,314740,320960,30). -snote(n173,[e,n],4,9:1,3/16,1/16,16.75,17.0,[1])-note(172,[e,n],4,315690,319860,320960,53). -snote(n174,[b,n],3,9:1,3/16,1/16,16.75,17.0,[3])-note(174,[b,n],3,316020,320320,320960,33). -snote(n175,[b,n],2,9:1,3/16,1/16,16.75,17.0,[4])-note(173,[b,n],2,315750,319890,320960,33). -snote(n176,[f,#],4,9:2,0,5/16,17.0,18.25,[1])-note(175,[f,#],4,319980,337300,352400,47). -snote(n177,[d,#],4,9:2,0,3/8,17.0,18.5,[2])-note(177,[d,#],4,320220,340720,352400,37). -snote(n178,[a,n],3,9:2,0,1/16,17.0,17.25,[3])-note(178,[a,n],3,320270,324990,324990,33). -snote(n179,[b,n],1,9:2,0,1/4,17.0,18.0,[7])-note(176,[b,n],1,320020,331090,335520,30). -snote(n180,[b,n],3,9:2,1/16,1/16,17.25,17.5,[3])-note(180,[b,n],3,324970,328860,335520,37). -snote(n181,[b,n],2,9:2,1/16,1/8,17.25,17.75,[4])-note(179,[b,n],2,324830,328830,335520,33). -snote(n182,[a,n],3,9:2,1/8,1/16,17.5,17.75,[3])-note(181,[a,n],3,328450,332220,335520,36). -snote(n183,[b,n],3,9:2,3/16,1/16,17.75,18.0,[3])-note(182,[b,n],3,332200,335920,335920,40). -snote(n184,[b,n],2,9:2,3/16,1/16,17.75,18.0,[4])-note(183,[b,n],2,332310,335740,335740,27). -snote(n185,[a,n],3,10:1,0,1/16,18.0,18.25,[3])-note(184,[a,n],3,335880,339910,352400,38). -snote(n186,[b,n],1,10:1,0,1/4,18.0,19.0,[7])-note(185,[b,n],1,336030,349380,352400,25). -snote(n187,[g,#],4,10:1,1/16,1/16,18.25,18.5,[1])-note(186,[g,#],4,339850,342070,352400,51). -snote(n188,[b,n],3,10:1,1/16,1/16,18.25,18.5,[3])-note(187,[b,n],3,339860,341790,352400,44). -snote(n189,[b,n],2,10:1,1/16,1/8,18.25,18.75,[4])-note(188,[b,n],2,339970,342970,352400,36). -snote(n190,[g,#],4,10:1,1/8,1/16,18.5,18.75,[1])-note(189,[g,#],4,343540,346420,352400,56). -snote(n191,[d,#],4,10:1,1/8,1/8,18.5,19.0,[2])-note(190,[d,#],4,343630,347830,352400,45). -snote(n192,[a,n],3,10:1,1/8,1/16,18.5,18.75,[3])-note(191,[a,n],3,343850,345830,352400,30). -snote(n193,[f,#],4,10:1,3/16,1/16,18.75,19.0,[1])-note(192,[f,#],4,347170,348880,352400,55). -snote(n194,[b,n],3,10:1,3/16,1/16,18.75,19.0,[3])-note(194,[b,n],3,347480,347890,352400,37). -snote(n195,[b,n],2,10:1,3/16,1/16,18.75,19.0,[4])-note(193,[b,n],2,347360,350150,352400,32). -snote(n196,[g,#],4,10:2,0,5/16,19.0,20.25,[1])-note(195,[g,#],4,351770,369660,384000,52). -snote(n197,[e,n],4,10:2,0,1/4,19.0,20.0,[2])-note(197,[e,n],4,352010,368560,368560,43). -snote(n198,[g,#],3,10:2,0,1/16,19.0,19.25,[3])-note(198,[g,#],3,352150,356980,356980,24). -snote(n199,[e,n],2,10:2,0,1/4,19.0,20.0,[7])-note(196,[e,n],2,351950,364240,367440,24). -snote(n200,[b,n],3,10:2,1/16,1/16,19.25,19.5,[3])-note(200,[b,n],3,356960,361320,367440,35). -snote(n201,[b,n],2,10:2,1/16,1/8,19.25,19.75,[4])-note(199,[b,n],2,356860,360570,367440,24). -snote(n202,[g,#],3,10:2,1/8,1/16,19.5,19.75,[3])-note(201,[g,#],3,360550,363900,367440,32). -snote(n203,[b,n],3,10:2,3/16,1/16,19.75,20.0,[3])-note(203,[b,n],3,364130,367640,367640,39). -snote(n204,[b,n],2,10:2,3/16,1/16,19.75,20.0,[4])-note(202,[b,n],2,364100,367700,367700,28). -snote(n205,[g,#],3,11:1,0,1/16,20.0,20.25,[3])-note(204,[g,#],3,367850,370540,384000,43). -snote(n206,[e,n],2,11:1,0,1/4,20.0,21.0,[7])-note(205,[e,n],2,367870,379290,384000,28). -snote(n207,[a,n],4,11:1,1/16,1/16,20.25,20.5,[1])-note(206,[a,n],4,371480,373510,384000,54). -snote(n208,[e,n],4,11:1,1/16,1/16,20.25,20.5,[3])-note(208,[e,n],4,371860,373290,384000,41). -snote(n209,[b,n],2,11:1,1/16,1/8,20.25,20.75,[4])-note(207,[b,n],2,371800,373960,384000,31). -snote(n210,[a,n],4,11:1,1/8,1/16,20.5,20.75,[1])-note(209,[a,n],4,375170,379120,384000,65). -snote(n211,[b,n],3,11:1,1/8,1/16,20.5,20.75,[3])-note(210,[b,n],3,375650,377960,384000,39). -snote(n212,[g,#],4,11:1,3/16,1/16,20.75,21.0,[1])-note(211,[g,#],4,378760,380320,384000,67). -snote(n213,[e,n],4,11:1,3/16,1/16,20.75,21.0,[3])-note(213,[e,n],4,379150,379470,384000,35). -snote(n214,[b,n],2,11:1,3/16,1/16,20.75,21.0,[4])-note(212,[b,n],2,379070,382480,384000,35). -snote(n215,[c,#],5,11:2,0,3/16,21.0,21.75,[1])-note(214,[c,#],5,383250,389340,400240,71). -snote(n216,[d,#],4,11:2,0,1/16,21.0,21.25,[3])-note(216,[d,#],4,383540,389220,400240,47). -snote(n217,[b,n],1,11:2,0,1/4,21.0,22.0,[7])-note(215,[b,n],1,383500,397030,400240,34). -snote(n218,[a,n],4,11:2,1/16,1/16,21.25,21.5,[3])-note(218,[a,n],4,387970,389590,400240,59). -snote(n219,[b,n],2,11:2,1/16,1/8,21.25,21.75,[4])-note(217,[b,n],2,387860,391610,400240,33). -snote(n220,[b,n],3,11:2,1/8,1/16,21.5,21.75,[3])-note(219,[b,n],3,391570,393950,400240,55). -snote(n221,[b,n],4,11:2,3/16,1/16,21.75,22.0,[1])-note(220,[b,n],4,395060,396060,400240,63). -snote(n222,[d,#],4,11:2,3/16,1/16,21.75,22.0,[3])-note(222,[d,#],4,395320,395910,400240,40). -snote(n223,[b,n],2,11:2,3/16,1/16,21.75,22.0,[4])-note(221,[b,n],2,395200,398730,400240,37). -snote(n224,[a,n],4,12:1,0,1/16,22.0,22.25,[1])-note(223,[a,n],4,399130,403990,415280,56). -snote(n225,[b,n],3,12:1,0,1/16,22.0,22.25,[3])-note(225,[b,n],3,399500,402590,402590,39). -snote(n226,[e,n],2,12:1,0,1/4,22.0,23.0,[7])-note(224,[e,n],2,399180,411860,415280,32). -snote(n227,[g,#],4,12:1,1/16,1/16,22.25,22.5,[1])-note(226,[g,#],4,403070,405340,415280,50). -snote(n228,[e,n],4,12:1,1/16,1/16,22.25,22.5,[3])-note(228,[e,n],4,403220,404220,415280,38). -snote(n229,[b,n],2,12:1,1/16,1/8,22.25,22.75,[4])-note(227,[b,n],2,403180,406490,415280,20). -snote(n230,[d,#],4,12:1,1/8,1/16,22.5,22.75,[1])-note(229,[d,#],4,406660,409660,415280,49). -snote(n231,[g,#],3,12:1,1/8,1/16,22.5,22.75,[3])-note(230,[g,#],3,407010,409020,415280,37). -snote(n232,[e,n],4,12:1,3/16,1/16,22.75,23.0,[1])-note(232,[e,n],4,410460,415280,415280,58). -snote(n233,[b,n],3,12:1,3/16,1/16,22.75,23.0,[3])-note(233,[b,n],3,410630,410990,415280,36). -snote(n234,[b,n],2,12:1,3/16,1/16,22.75,23.0,[4])-note(231,[b,n],2,410450,413500,415280,32). -snote(n235,[f,#],4,12:2,0,5/16,23.0,24.25,[1])-note(234,[f,#],4,415140,432370,434960,55). -snote(n236,[c,#],4,12:2,0,1/16,23.0,23.25,[2])-note(237,[c,#],4,415610,419990,420000,32). -snote(n237,[a,n],3,12:2,0,1/16,23.0,23.25,[3])-note(236,[a,n],3,415510,417610,417610,31). -snote(n238,[b,n],1,12:2,0,1/4,23.0,24.0,[7])-note(235,[b,n],1,415510,427820,430480,26). -snote(n239,[d,#],4,12:2,1/16,1/16,23.25,23.5,[2])-note(238,[d,#],4,419940,423640,430480,40). -snote(n240,[b,n],3,12:2,1/16,1/16,23.25,23.5,[3])-note(240,[b,n],3,420340,421670,421670,29). -snote(n241,[b,n],2,12:2,1/16,1/8,23.25,23.75,[4])-note(239,[b,n],2,420200,424390,430480,27). -snote(n242,[c,#],4,12:2,1/8,1/16,23.5,23.75,[2])-note(241,[c,#],4,423600,427480,430480,44). -snote(n243,[a,n],3,12:2,1/8,1/16,23.5,23.75,[3])-note(242,[a,n],3,423890,424800,430480,34). -snote(n244,[d,#],4,12:2,3/16,1/16,23.75,24.0,[2])-note(243,[d,#],4,427410,431500,431500,49). -snote(n245,[b,n],3,12:2,3/16,1/16,23.75,24.0,[3])-note(245,[b,n],3,427740,428760,430480,24). -snote(n246,[b,n],2,12:2,3/16,1/16,23.75,24.0,[4])-note(244,[b,n],2,427430,431060,431060,34). -snote(n247,[c,#],4,13:1,0,1/16,24.0,24.25,[2])-note(246,[c,#],4,431030,435080,435080,49). -snote(n248,[a,n],3,13:1,0,1/16,24.0,24.25,[3])-note(248,[a,n],3,431150,432820,434960,40). -snote(n249,[b,n],1,13:1,0,1/4,24.0,25.0,[7])-note(247,[b,n],1,431110,443880,447360,28). -snote(n250,[g,#],4,13:1,1/16,1/16,24.25,24.5,[1])-note(249,[g,#],4,434940,437570,447360,62). -snote(n251,[d,#],4,13:1,1/16,1/16,24.25,24.5,[2])-note(252,[d,#],4,435310,437110,447360,30). -snote(n252,[b,n],3,13:1,1/16,1/16,24.25,24.5,[3])-note(251,[b,n],3,435270,436450,436450,34). -snote(n253,[b,n],2,13:1,1/16,1/8,24.25,24.75,[4])-note(250,[b,n],2,435220,438320,447360,31). -snote(n254,[g,#],4,13:1,1/8,1/16,24.5,24.75,[1])-note(253,[g,#],4,438560,441880,447360,63). -snote(n255,[c,#],4,13:1,1/8,1/16,24.5,24.75,[2])-note(254,[c,#],4,438850,441160,447360,45). -snote(n256,[a,n],3,13:1,1/8,1/16,24.5,24.75,[3])-note(255,[a,n],3,438970,439590,447360,22). -snote(n257,[f,#],4,13:1,3/16,1/16,24.75,25.0,[1])-note(256,[f,#],4,442240,443790,447360,59). -snote(n258,[d,#],4,13:1,3/16,1/16,24.75,25.0,[2])-note(259,[d,#],4,442440,443200,447360,44). -snote(n259,[b,n],3,13:1,3/16,1/16,24.75,25.0,[3])-note(258,[b,n],3,442420,442900,447360,45). -snote(n260,[b,n],2,13:1,3/16,1/16,24.75,25.0,[4])-note(257,[b,n],2,442380,445390,447360,39). -snote(n261,[e,n],4,13:2,0,1/4,25.0,26.0,[1])-note(260,[e,n],4,446780,459350,463280,59). -snote(n262,[g,#],3,13:2,0,1/16,25.0,25.25,[3])-note(262,[g,#],3,447280,451210,451210,29). -snote(n263,[e,n],2,13:2,0,1/4,25.0,26.0,[7])-note(261,[e,n],2,447140,456720,463280,26). -snote(n264,[b,n],3,13:2,1/16,1/16,25.25,25.5,[3])-note(263,[b,n],3,450860,454770,463280,40). -snote(n265,[b,n],2,13:2,1/16,1/8,25.25,25.75,[4])-note(264,[b,n],2,450870,453570,463280,24). -snote(n266,[g,#],3,13:2,1/8,1/16,25.5,25.75,[3])-note(265,[g,#],3,454420,455480,463280,38). -snote(n267,[b,n],3,13:2,3/16,1/16,25.75,26.0,[3])-note(266,[b,n],3,458520,459050,463280,31). -snote(n268,[b,n],2,13:2,3/16,1/16,25.75,26.0,[4])-note(267,[b,n],2,458590,459390,463280,27). -snote(n269,[b,n],4,14:1,0,1/16,26.0,26.25,[1])-note(268,[b,n],4,463190,465830,478400,53). -snote(n270,[f,#],4,14:1,0,1/16,26.0,26.25,[2])-note(270,[f,#],4,463410,466730,478400,31). -snote(n271,[d,n],4,14:1,0,1/16,26.0,26.25,[3])-note(271,[d,n],4,463580,465880,478400,31). -snote(n272,[e,n],2,14:1,0,1/4,26.0,27.0,[7])-note(269,[e,n],2,463350,476010,478400,31). -snote(n273,[c,#],5,14:1,1/16,1/16,26.25,26.5,[1])-note(272,[c,#],5,467690,469920,478400,56). -snote(n274,[g,#],4,14:1,1/16,1/16,26.25,26.5,[2])-note(274,[g,#],4,467920,469900,478400,43). -snote(n275,[e,n],4,14:1,1/16,1/16,26.25,26.5,[3])-note(275,[e,n],4,468100,468990,478400,38). -snote(n276,[e,n],3,14:1,1/16,1/8,26.25,26.75,[4])-note(273,[e,n],3,467810,470520,478400,34). -snote(n277,[c,#],5,14:1,1/8,1/16,26.5,26.75,[1])-note(276,[c,#],5,471080,473310,478400,63). -snote(n278,[f,#],4,14:1,1/8,1/16,26.5,26.75,[2])-note(277,[f,#],4,471310,473710,478400,47). -snote(n279,[d,n],4,14:1,1/8,1/16,26.5,26.75,[3])-note(278,[d,n],4,471340,472280,478400,44). -snote(n280,[b,n],4,14:1,3/16,1/16,26.75,27.0,[1])-note(279,[b,n],4,474630,475960,478400,64). -snote(n281,[g,#],4,14:1,3/16,1/16,26.75,27.0,[2])-note(281,[g,#],4,474820,476290,478400,46). -snote(n282,[e,n],4,14:1,3/16,1/16,26.75,27.0,[3])-note(282,[e,n],4,474850,475130,478400,37). -snote(n283,[e,n],3,14:1,3/16,1/16,26.75,27.0,[4])-note(280,[e,n],3,474710,477230,478400,42). -snote(n284,[a,n],4,14:2,0,1/16,27.0,27.25,[1])-note(283,[a,n],4,478030,481940,491520,62). -snote(n285,[c,#],4,14:2,0,1/16,27.0,27.25,[3])-note(285,[c,#],4,478280,481560,491520,44). -snote(n286,[a,n],2,14:2,0,1/4,27.0,28.0,[7])-note(284,[a,n],2,478150,487490,491520,39). -snote(n287,[b,n],4,14:2,1/16,1/16,27.25,27.5,[1])-note(287,[b,n],4,481540,484830,491520,67). -snote(n288,[e,n],4,14:2,1/16,1/16,27.25,27.5,[3])-note(288,[e,n],4,481680,484470,491520,54). -snote(n289,[e,n],3,14:2,1/16,1/8,27.25,27.75,[4])-note(286,[e,n],3,481470,484090,491520,39). -snote(n290,[g,#],4,14:2,1/8,1/16,27.5,27.75,[1])-note(289,[g,#],4,484610,488020,491520,65). -snote(n291,[c,#],4,14:2,1/8,1/16,27.5,27.75,[3])-note(290,[c,#],4,484810,485690,491520,56). -snote(n292,[a,n],4,14:2,3/16,1/16,27.75,28.0,[1])-note(291,[a,n],4,487970,488910,491520,69). -snote(n293,[e,n],4,14:2,3/16,1/16,27.75,28.0,[3])-note(293,[e,n],4,488090,488420,491520,56). -snote(n294,[e,n],3,14:2,3/16,1/16,27.75,28.0,[4])-note(292,[e,n],3,487990,488850,491520,48). -snote(n295,[d,#],5,15:1,0,1/16,28.0,28.25,[1])-note(294,[d,#],5,491370,495960,505840,73). -snote(n296,[b,#],4,15:1,0,1/16,28.0,28.25,[2])-note(296,[b,#],4,491500,495660,505840,62). -snote(n297,[f,#],4,15:1,0,1/16,28.0,28.25,[3])-note(297,[f,#],4,491550,493450,505840,55). -snote(n298,[g,#],2,15:1,0,1/4,28.0,29.0,[7])-note(295,[g,#],2,491480,506040,506040,55). -snote(n299,[e,n],5,15:1,1/16,1/16,28.25,28.5,[1])-note(298,[e,n],5,495490,497460,505840,74). -snote(n300,[g,#],4,15:1,1/16,1/16,28.25,28.5,[3])-note(300,[g,#],4,495700,497400,505840,52). -snote(n301,[g,#],3,15:1,1/16,1/8,28.25,28.75,[4])-note(299,[g,#],3,495600,498690,505840,44). -snote(n302,[e,n],5,15:1,1/8,1/16,28.5,28.75,[1])-note(301,[e,n],5,498750,501510,505840,71). -snote(n303,[a,#],4,15:1,1/8,1/16,28.5,28.75,[2])-note(302,[a,#],4,498860,499810,505840,60). -snote(n304,[f,#],4,15:1,1/8,1/16,28.5,28.75,[3])-note(303,[f,#],4,498980,500290,505840,52). -snote(n305,[d,#],5,15:1,3/16,1/16,28.75,29.0,[1])-note(304,[d,#],5,501960,502920,505840,74). -snote(n306,[b,#],4,15:1,3/16,1/16,28.75,29.0,[2])-note(307,[b,#],4,502120,502380,505840,51). -snote(n307,[g,#],4,15:1,3/16,1/16,28.75,29.0,[3])-note(305,[g,#],4,502080,502730,505840,59). -snote(n308,[g,#],3,15:1,3/16,1/16,28.75,29.0,[4])-note(306,[g,#],3,502110,504240,505840,52). -snote(n309,[c,#],5,15:2,0,1/16,29.0,29.25,[1])-note(309,[c,#],5,505410,509170,519840,75). -snote(n310,[e,n],4,15:2,0,1/16,29.0,29.25,[3])-note(310,[e,n],4,505500,509030,519840,62). -snote(n311,[c,#],3,15:2,0,1/4,29.0,30.0,[7])-note(308,[c,#],3,505390,516460,519840,52). -snote(n312,[d,#],5,15:2,1/16,1/16,29.25,29.5,[1])-note(311,[d,#],5,508820,511110,519840,76). -snote(n313,[g,#],4,15:2,1/16,1/16,29.25,29.5,[3])-note(313,[g,#],4,508990,511940,519840,56). -snote(n314,[g,#],3,15:2,1/16,1/8,29.25,29.75,[4])-note(312,[g,#],3,508840,511820,519840,49). -snote(n315,[c,n],5,15:2,1/8,1/16,29.5,29.75,[1])-note(314,[c,n],5,512030,516370,519840,72). -snote(n316,[e,n],4,15:2,1/8,1/16,29.5,29.75,[3])-note(315,[e,n],4,512200,514620,519840,60). -snote(n317,[c,#],5,15:2,3/16,1/16,29.75,30.0,[1])-note(316,[c,#],5,515590,516720,519840,75). -snote(n318,[g,#],4,15:2,3/16,1/16,29.75,30.0,[3])-note(318,[g,#],4,515650,516190,519840,61). -snote(n319,[g,#],3,15:2,3/16,1/16,29.75,30.0,[4])-note(317,[g,#],3,515610,516670,519840,57). -snote(n320,[e,n],5,16:1,0,1/16,30.0,30.25,[1])-note(319,[e,n],5,519870,522340,534400,80). -snote(n321,[e,n],4,16:1,0,1/16,30.0,30.25,[2])-note(322,[e,n],4,520030,521750,534400,61). -snote(n322,[a,#],4,16:1,0,1/16,30.0,30.25,[3])-note(321,[a,#],4,520020,522790,534400,58). -snote(n323,[a,#],3,16:1,0,1/16,30.0,30.25,[4])-note(320,[a,#],3,519980,522610,534400,45). -snote(n324,[f,#],3,16:1,0,1/16,30.0,30.25,[5])-note(325,[f,#],3,520140,522870,534400,39). -snote(n325,[e,n],3,16:1,0,1/16,30.0,30.25,[6])-note(324,[e,n],3,520130,522730,534400,40). -snote(n326,[c,#],3,16:1,0,1/16,30.0,30.25,[7])-note(323,[c,#],3,520080,522730,534400,43). -snote(n327,[f,#],5,16:1,1/16,1/16,30.25,30.5,[1])-note(326,[f,#],5,524190,525480,534400,78). -snote(n328,[a,#],4,16:1,1/16,1/16,30.25,30.5,[2])-note(332,[a,#],4,524470,526220,534400,57). -snote(n329,[f,#],4,16:1,1/16,1/16,30.25,30.5,[3])-note(328,[f,#],4,524270,525680,534400,69). -snote(n330,[a,#],3,16:1,1/16,1/16,30.25,30.5,[4])-note(327,[a,#],3,524240,525730,534400,58). -snote(n331,[f,#],3,16:1,1/16,1/16,30.25,30.5,[5])-note(329,[f,#],3,524370,526030,534400,45). -snote(n332,[e,n],3,16:1,1/16,1/16,30.25,30.5,[6])-note(330,[e,n],3,524400,526060,534400,42). -snote(n333,[c,#],3,16:1,1/16,1/16,30.25,30.5,[7])-note(331,[c,#],3,524410,525630,534400,44). -snote(n334,[d,#],5,16:1,1/8,1/16,30.5,30.75,[1])-note(334,[d,#],5,527630,529870,534400,83). -snote(n335,[a,#],4,16:1,1/8,1/16,30.5,30.75,[2])-note(339,[a,#],4,527890,528240,534400,50). -snote(n336,[d,#],4,16:1,1/8,1/16,30.5,30.75,[3])-note(335,[d,#],4,527710,528890,534400,80). -snote(n337,[a,#],3,16:1,1/8,1/16,30.5,30.75,[4])-note(333,[a,#],3,527620,528970,534400,68). -snote(n338,[f,#],3,16:1,1/8,1/16,30.5,30.75,[5])-note(337,[f,#],3,527780,529190,534400,51). -snote(n339,[e,n],3,16:1,1/8,1/16,30.5,30.75,[6])-note(338,[e,n],3,527840,529460,534400,44). -snote(n340,[c,#],3,16:1,1/8,1/16,30.5,30.75,[7])-note(336,[c,#],3,527720,529600,534400,58). -snote(n341,[e,n],5,16:1,3/16,1/16,30.75,31.0,[1])-note(341,[e,n],5,530940,532040,534400,87). -snote(n342,[a,#],4,16:1,3/16,1/16,30.75,31.0,[2])-note(344,[a,#],4,530990,532200,534400,61). -snote(n343,[e,n],4,16:1,3/16,1/16,30.75,31.0,[3])-note(343,[e,n],4,530960,531570,534400,77). -snote(n344,[a,#],3,16:1,3/16,1/16,30.75,31.0,[4])-note(340,[a,#],3,530890,532250,534400,67). -snote(n345,[f,#],3,16:1,3/16,1/16,30.75,31.0,[5])-note(345,[f,#],3,531060,532350,534400,53). -snote(n346,[e,n],3,16:1,3/16,1/16,30.75,31.0,[6])-note(346,[e,n],3,531180,533570,534400,42). -snote(n347,[c,#],3,16:1,3/16,1/16,30.75,31.0,[7])-note(342,[c,#],3,530960,532300,534400,55). -snote(n348,[f,#],5,16:2,0,1/16,31.0,31.25,[1])-note(347,[f,#],5,534540,536600,541440,85). -snote(n349,[f,#],4,16:2,0,1/16,31.0,31.25,[2])-note(350,[f,#],4,534600,536410,541440,75). -snote(n350,[a,#],4,16:2,0,1/16,31.0,31.25,[3])-note(351,[a,#],4,534620,540680,541440,73). -snote(n351,[a,#],3,16:2,0,1/16,31.0,31.25,[4])-note(348,[a,#],3,534550,536830,541440,67). -snote(n352,[f,#],3,16:2,0,1/16,31.0,31.25,[5])-note(349,[f,#],3,534600,539640,541440,57). -snote(n353,[e,n],3,16:2,0,1/16,31.0,31.25,[6])-note(353,[e,n],3,534720,536980,541440,57). -snote(n354,[c,n],3,16:2,0,1/16,31.0,31.25,[7])-note(352,[c,n],3,534670,536880,541440,61). -snote(n355,[g,#],5,16:2,1/16,1/16,31.25,31.5,[1])-note(354,[g,#],5,538260,539290,541440,87). -snote(n356,[a,#],4,16:2,1/16,1/16,31.25,31.5,[2])-deletion. -snote(n357,[g,#],4,16:2,1/16,1/16,31.25,31.5,[3])-note(358,[g,#],4,538380,539390,541440,73). -snote(n358,[a,#],3,16:2,1/16,1/16,31.25,31.5,[4])-note(355,[a,#],3,538270,539300,541440,68). -snote(n359,[f,#],3,16:2,1/16,1/16,31.25,31.5,[5])-deletion. -snote(n360,[e,n],3,16:2,1/16,1/16,31.25,31.5,[6])-note(357,[e,n],3,538360,539860,541440,56). -snote(n361,[c,n],3,16:2,1/16,1/16,31.25,31.5,[7])-note(356,[c,n],3,538350,539110,541440,58). -snote(n362,[e,n],5,16:2,1/8,1/16,31.5,31.75,[1])-note(359,[e,n],5,541670,543030,545760,87). -snote(n363,[a,#],4,16:2,1/8,1/16,31.5,31.75,[2])-note(364,[a,#],4,541810,543300,545760,66). -snote(n364,[e,n],4,16:2,1/8,1/16,31.5,31.75,[3])-note(361,[e,n],4,541720,542640,545760,82). -snote(n365,[a,#],3,16:2,1/8,1/16,31.5,31.75,[4])-note(360,[a,#],3,541690,542980,545760,77). -snote(n366,[c,n],3,16:2,1/8,1/16,31.5,31.75,[5])-note(362,[c,n],3,541800,543030,545760,65). -snote(n367,[f,#],3,16:2,1/8,1/16,31.5,31.75,[6])-note(363,[f,#],3,541800,543350,545760,65). -snote(n368,[e,n],3,16:2,1/8,1/16,31.5,31.75,[7])-note(365,[e,n],3,541890,543490,545760,57). -snote(n369,[f,#],5,16:2,3/16,1/16,31.75,32.0,[1])-note(367,[f,#],5,545860,547830,553280,87). -snote(n370,[a,#],4,16:2,3/16,1/16,31.75,32.0,[2])-note(369,[a,#],4,545900,548120,553280,78). -snote(n371,[f,#],4,16:2,3/16,1/16,31.75,32.0,[3])-note(368,[f,#],4,545890,547630,553280,82). -snote(n372,[a,#],3,16:2,3/16,1/16,31.75,32.0,[4])-note(366,[a,#],3,545860,547790,553280,75). -snote(n373,[f,#],3,16:2,3/16,1/16,31.75,32.0,[5])-note(370,[f,#],3,545920,548050,553280,66). -snote(n374,[e,n],3,16:2,3/16,1/16,31.75,32.0,[6])-note(372,[e,n],3,546130,548300,553280,50). -snote(n375,[c,n],3,16:2,3/16,1/16,31.75,32.0,[7])-note(371,[c,n],3,545950,547700,553280,66). -snote(n376,[g,#],5,17:1,0,5/16,32.0,33.25,[1])-note(373,[g,#],5,552190,570300,577840,87). -snote(n377,[e,n],5,17:1,0,1/4,32.0,33.0,[2])-note(378,[e,n],5,552330,571070,577840,69). -snote(n378,[g,#],4,17:1,0,1/16,32.0,32.25,[3])-note(374,[g,#],4,552280,558460,558460,71). -snote(n379,[b,n],3,17:1,0,1/2,32.0,34.0,[4])-note(375,[b,n],3,552300,583580,588160,63). -snote(n380,[g,#],3,17:1,0,1/2,32.0,34.0,[5])-note(377,[g,#],3,552330,583800,588160,56). -snote(n381,[e,n],3,17:1,0,1/2,32.0,34.0,[6])-note(379,[e,n],3,552420,584470,588160,50). -snote(n382,[b,n],2,17:1,0,1/2,32.0,34.0,[7])-note(376,[b,n],2,552330,584020,588160,59). -snote(n383,[b,n],4,17:1,1/16,1/16,32.25,32.5,[3])-note(380,[b,n],4,558320,563200,577840,62). -snote(n384,[g,#],4,17:1,1/8,1/16,32.5,32.75,[3])-note(381,[g,#],4,562690,566540,577840,56). -snote(n385,[b,n],4,17:1,3/16,1/16,32.75,33.0,[3])-note(382,[b,n],4,566410,570570,577840,60). -snote(n386,[g,#],4,17:2,0,1/16,33.0,33.25,[3])-note(383,[g,#],4,569890,571670,577840,56). -snote(n387,[f,#],5,17:2,1/16,1/16,33.25,33.5,[1])-note(384,[f,#],5,573630,575600,577840,67). -snote(n388,[b,n],4,17:2,1/16,1/16,33.25,33.5,[3])-note(385,[b,n],4,573880,576850,577840,48). -snote(n389,[e,n],5,17:2,1/8,1/16,33.5,33.75,[1])-note(386,[e,n],5,577600,581790,581790,66). -snote(n390,[g,#],4,17:2,1/8,1/16,33.5,33.75,[3])-note(387,[g,#],4,577930,580600,581680,40). -snote(n391,[c,#],5,17:2,3/16,1/16,33.75,34.0,[1])-note(388,[c,#],5,581900,584080,588160,53). -snote(n392,[b,n],4,17:2,3/16,1/16,33.75,34.0,[3])-note(389,[b,n],4,581970,583060,588160,48). -snote(n393,[d,#],5,18:1,0,1/4,34.0,35.0,[1])-note(390,[d,#],5,587360,600920,604400,59). -snote(n394,[d,#],4,18:1,0,1/16,34.0,34.25,[3])-note(391,[d,#],4,587670,592590,592590,45). -snote(n395,[f,#],3,18:1,0,1/16,34.0,34.25,[7])-note(392,[f,#],3,587790,601180,604400,25). -snote(n396,[f,#],4,18:1,1/16,1/16,34.25,34.5,[3])-note(393,[f,#],4,592710,597610,604400,39). -snote(n397,[b,n],3,18:1,1/16,1/16,34.25,34.5,[4])-note(394,[b,n],3,592880,596330,604400,28). -snote(n398,[d,#],4,18:1,1/8,1/16,34.5,34.75,[3])-note(395,[d,#],4,596710,600240,604400,36). -snote(n399,[b,n],2,18:1,1/8,1/16,34.5,34.75,[7])-note(396,[b,n],2,596750,600210,604400,13). -snote(n400,[f,#],4,18:1,3/16,1/16,34.75,35.0,[3])-note(397,[f,#],4,600010,601710,604400,46). -snote(n401,[b,n],3,18:1,3/16,1/16,34.75,35.0,[4])-note(398,[b,n],3,600310,603310,604400,38). -snote(n402,[e,n],5,18:2,0,1/16,35.0,35.25,[1])-note(399,[e,n],5,603670,608440,608440,62). -snote(n403,[e,n],4,18:2,0,1/16,35.0,35.25,[3])-note(400,[e,n],4,603870,607870,607870,46). -snote(n404,[c,#],3,18:2,0,1/16,35.0,35.25,[7])-note(401,[c,#],3,603890,608550,608550,35). -snote(n405,[d,#],5,18:2,1/16,1/16,35.25,35.5,[1])-note(402,[d,#],5,607250,611630,611630,66). -snote(n406,[g,#],4,18:2,1/16,1/16,35.25,35.5,[3])-note(403,[g,#],4,607380,611250,611250,50). -snote(n407,[g,#],3,18:2,1/16,1/16,35.25,35.5,[4])-note(404,[g,#],3,607610,611150,611150,32). -snote(n408,[c,#],5,18:2,1/8,1/16,35.5,35.75,[1])-note(405,[c,#],5,610840,615680,620400,62). -snote(n409,[e,n],4,18:2,1/8,1/16,35.5,35.75,[3])-note(406,[e,n],4,610960,613670,614560,44). -snote(n410,[g,#],2,18:2,1/8,1/4,35.5,36.5,[7])-note(407,[g,#],2,611000,617540,620400,35). -snote(n411,[g,#],4,18:2,3/16,1/16,35.75,36.0,[1])-note(408,[g,#],4,614910,616620,620400,52). -snote(n412,[g,#],3,18:2,3/16,1/16,35.75,36.0,[4])-note(409,[g,#],3,614920,617420,620400,33). -snote(n413,[b,n],4,19:1,0,1/4,36.0,37.0,[1])-note(410,[b,n],4,619990,631930,636160,54). -snote(n414,[b,n],3,19:1,0,1/16,36.0,36.25,[3])-note(412,[b,n],3,620360,624530,627760,36). -snote(n415,[d,#],3,19:1,0,1/16,36.0,36.25,[7])-note(411,[d,#],3,620000,633190,636160,31). -snote(n416,[d,#],4,19:1,1/16,1/16,36.25,36.5,[3])-note(413,[d,#],4,624210,628420,628420,40). -snote(n417,[g,#],3,19:1,1/16,1/16,36.25,36.5,[4])-note(414,[g,#],3,624340,627580,627760,29). -snote(n418,[b,n],3,19:1,1/8,1/16,36.5,36.75,[3])-note(416,[b,n],3,627920,631410,636160,36). -snote(n419,[g,#],2,19:1,1/8,1/16,36.5,36.75,[7])-note(415,[g,#],2,627800,631310,636160,30). -snote(n420,[d,#],4,19:1,3/16,1/16,36.75,37.0,[3])-note(417,[d,#],4,631760,632320,636160,43). -snote(n421,[g,#],3,19:1,3/16,1/16,36.75,37.0,[4])-note(418,[g,#],3,631870,632380,636160,7). -snote(n422,[c,#],5,19:2,0,1/16,37.0,37.25,[1])-note(419,[c,#],5,636150,638250,640480,54). -snote(n423,[c,#],4,19:2,0,1/16,37.0,37.25,[3])-note(421,[c,#],4,636510,640610,640610,35). -snote(n424,[a,n],2,19:2,0,1/16,37.0,37.25,[7])-note(420,[a,n],2,636430,640560,640560,30). -snote(n425,[b,n],4,19:2,1/16,1/16,37.25,37.5,[1])-note(423,[b,n],4,640500,644420,644420,48). -snote(n426,[e,n],4,19:2,1/16,1/16,37.25,37.5,[3])-note(424,[e,n],4,640810,643960,644000,31). -snote(n427,[e,n],3,19:2,1/16,1/16,37.25,37.5,[4])-note(422,[e,n],3,640470,644430,644430,37). -snote(n428,[a,n],4,19:2,1/8,1/16,37.5,37.75,[1])-note(425,[a,n],4,644100,648980,653120,48). -snote(n429,[c,#],4,19:2,1/8,1/16,37.5,37.75,[3])-note(427,[c,#],4,644370,647270,647680,23). -snote(n430,[e,n],2,19:2,1/8,1/4,37.5,38.5,[7])-note(426,[e,n],2,644250,649890,653120,32). -snote(n431,[e,n],4,19:2,3/16,1/16,37.75,38.0,[1])-note(428,[e,n],4,647900,649420,653120,49). -snote(n432,[e,n],3,19:2,3/16,1/16,37.75,38.0,[4])-note(429,[e,n],3,647930,650500,653120,32). -snote(n433,[g,#],4,20:1,0,1/2,38.0,40.0,[1])-note(430,[g,#],4,652680,685200,692640,46). -snote(n434,[g,#],3,20:1,0,1/16,38.0,38.25,[3])-note(432,[g,#],3,653160,657670,657670,26). -snote(n435,[b,n],2,20:1,0,1/16,38.0,38.25,[7])-note(431,[b,n],2,652910,663200,663200,23). -snote(n436,[b,n],3,20:1,1/16,1/16,38.25,38.5,[3])-note(434,[b,n],3,657880,662140,662140,30). -snote(n437,[e,n],3,20:1,1/16,1/16,38.25,38.5,[4])-note(433,[e,n],3,657710,661710,662000,19). -snote(n438,[g,#],3,20:1,1/8,1/16,38.5,38.75,[3])-note(436,[g,#],3,661960,665660,669840,15). -snote(n439,[e,n],2,20:1,1/8,1/4,38.5,39.5,[7])-note(435,[e,n],2,661710,670300,670300,23). -snote(n440,[b,n],3,20:1,3/16,1/16,38.75,39.0,[3])-note(438,[b,n],3,665720,670130,670130,34). -snote(n441,[e,n],3,20:1,3/16,1/16,38.75,39.0,[4])-note(437,[e,n],3,665520,670230,670230,31). -snote(n442,[g,#],3,20:2,0,1/16,39.0,39.25,[3])-note(440,[g,#],3,669760,673640,678240,25). -snote(n443,[b,n],2,20:2,0,1/16,39.0,39.25,[7])-note(439,[b,n],2,669610,679190,679190,28). -snote(n444,[b,n],3,20:2,1/16,1/16,39.25,39.5,[3])-note(441,[b,n],3,673620,678870,678870,27). -snote(n445,[e,n],3,20:2,1/16,1/16,39.25,39.5,[4])-note(442,[e,n],3,673980,678130,678240,19). -snote(n446,[g,#],3,20:2,1/8,1/16,39.5,39.75,[3])-note(443,[g,#],3,678140,683800,692640,23). -snote(n447,[e,n],2,20:2,1/8,3/8,39.5,41.0,[7])-note(444,[e,n],2,678170,704670,710320,22). -snote(n448,[b,n],3,20:2,3/16,1/16,39.75,40.0,[3])-note(445,[b,n],3,683550,685070,692640,30). -snote(n449,[b,n],2,20:2,3/16,5/16,39.75,41.0,[6])-note(446,[b,n],2,683590,705800,710320,18). -snote(n450,[g,#],4,21:1,0,0,40.0,40.0,[1,grace])-note(447,[g,#],4,691330,694180,694180,38). -snote(n451,[f,#],4,21:1,0,0,40.0,40.0,[1,grace])-note(450,[f,#],4,693140,695700,695700,44). -snote(n452,[e,n],4,21:1,0,1/4,40.0,41.0,[1])-note(451,[e,n],4,695050,705530,710320,40). -snote(n453,[g,#],3,21:1,0,1/4,40.0,41.0,[3])-note(449,[g,#],3,691800,703570,710320,28). -snote(n454,[e,n],3,21:1,0,1/4,40.0,41.0,[4])-deletion. +scoreprop(keySignature,E,0:1,0,-0.5000). +scoreprop(timeSignature,2/4,0:1,0,-0.5000). +snote(n1,[B,n],3,0:1,0,1/8,-0.5000,0.0000,[v1,staff1])-note(n0,59,39940,42140,44,1,0). +snote(n2,[E,n],4,1:1,0,1/8,0.0000,0.5000,[v1,staff1])-note(n2,64,45630,55280,54,1,0). +snote(n3,[G,#],3,1:1,0,1/16,0.0000,0.2500,[v3,staff1])-note(n3,56,46210,51830,26,1,0). +snote(n4,[E,n],2,1:1,0,1/4,0.0000,1.0000,[v7,staff2])-note(n1,40,45620,60390,22,1,0). +snote(n5,[B,n],3,1:1,1/16,1/16,0.2500,0.5000,[v3,staff1])-note(n5,59,51570,56570,37,1,0). +snote(n6,[B,n],2,1:1,1/16,1/8,0.2500,0.7500,[v4,staff2,accent])-note(n4,47,51460,55780,20,1,0). +snote(n7,[D,#],4,1:1,1/8,1/16,0.5000,0.7500,[v1,staff1])-note(n6,63,55780,60210,52,1,0). +snote(n8,[G,#],3,1:1,1/8,1/16,0.5000,0.7500,[v3,staff1])-note(n7,56,56100,58580,32,1,0). +snote(n9,[E,n],4,1:1,3/16,1/16,0.7500,1.0000,[v1,staff1])-note(n8,64,59810,64900,59,1,0). +snote(n10,[B,n],3,1:1,3/16,1/16,0.7500,1.0000,[v3,staff1])-note(n9,59,60020,64520,41,1,0). +snote(n11,[B,n],2,1:1,3/16,1/16,0.7500,1.0000,[v4,staff2])-note(n10,47,60150,64600,26,1,0). +snote(n12,[F,#],4,1:2,0,5/16,1.0000,2.2500,[v1,staff1])-note(n11,66,64270,83250,58,1,0). +snote(n13,[D,#],4,1:2,0,3/8,1.0000,2.5000,[v2,staff1])-note(n13,63,64550,85870,41,1,0). +snote(n14,[A,n],3,1:2,0,1/16,1.0000,1.2500,[v3,staff1])-note(n14,57,64720,69720,32,1,0). +snote(n15,[B,n],1,1:2,0,1/4,1.0000,2.0000,[v7,staff2])-note(n12,35,64550,76630,30,1,0). +snote(n16,[B,n],3,1:2,1/16,1/16,1.2500,1.5000,[v3,staff1])-note(n16,59,69850,74250,36,1,0). +snote(n17,[B,n],2,1:2,1/16,1/8,1.2500,1.7500,[v4,staff2,accent])-note(n15,47,69630,73620,31,1,0). +snote(n18,[A,n],3,1:2,1/8,1/16,1.5000,1.7500,[v3,staff1])-note(n17,57,73660,77370,40,1,0). +snote(n19,[B,n],3,1:2,3/16,1/16,1.7500,2.0000,[v3,staff1])-note(n19,59,77360,81790,44,1,0). +snote(n20,[B,n],2,1:2,3/16,1/16,1.7500,2.0000,[v4,staff2])-note(n18,47,77330,81190,30,1,0). +snote(n21,[A,n],3,2:1,0,1/16,2.0000,2.2500,[v3,staff1])-note(n20,57,81100,84910,39,1,0). +snote(n22,[B,n],1,2:1,0,1/4,2.0000,3.0000,[v7,staff2])-note(n21,35,81120,94120,30,1,0). +snote(n23,[G,#],4,2:1,1/16,1/16,2.2500,2.5000,[v1,staff1])-note(n22,68,85040,87070,57,1,0). +snote(n24,[B,n],3,2:1,1/16,1/16,2.2500,2.5000,[v3,staff1])-note(n23,59,85070,86590,44,1,0). +snote(n25,[B,n],2,2:1,1/16,1/8,2.2500,2.7500,[v4,staff2,accent])-note(n24,47,85220,88030,31,1,0). +snote(n26,[G,#],4,2:1,1/8,1/16,2.5000,2.7500,[v1,staff1])-note(n25,68,88770,91740,63,1,0). +snote(n27,[D,#],4,2:1,1/8,1/8,2.5000,3.0000,[v2,staff1])-note(n26,63,88930,93360,48,1,0). +snote(n28,[A,n],3,2:1,1/8,1/16,2.5000,2.7500,[v3,staff1])-note(n27,57,89400,91580,11,1,0). +snote(n29,[F,#],4,2:1,3/16,1/16,2.7500,3.0000,[v1,staff1])-note(n28,66,92480,94170,65,1,0). +snote(n30,[B,n],3,2:1,3/16,1/16,2.7500,3.0000,[v3,staff1])-note(n29,59,92730,93270,45,1,0). +snote(n31,[B,n],2,2:1,3/16,1/16,2.7500,3.0000,[v4,staff2])-note(n30,47,92780,94760,31,1,0). +snote(n32,[G,#],4,2:2,0,5/16,3.0000,4.2500,[v1,staff1,accent])-note(n31,68,97270,115460,61,1,0). +snote(n33,[E,n],4,2:2,0,1/4,3.0000,4.0000,[v2,staff1])-note(n33,64,97500,114040,48,1,0). +snote(n34,[G,#],3,2:2,0,1/16,3.0000,3.2500,[v3,staff1])-note(n34,56,97580,102500,33,1,0). +snote(n35,[E,n],2,2:2,0,1/4,3.0000,4.0000,[v7,staff2])-note(n32,40,97450,109830,29,1,0). +snote(n36,[B,n],3,2:2,1/16,1/16,3.2500,3.5000,[v3,staff1])-note(n36,59,102570,106790,38,1,0). +snote(n37,[B,n],2,2:2,1/16,1/8,3.2500,3.7500,[v4,staff2,accent])-note(n35,47,102560,106020,27,1,0). +snote(n38,[G,#],3,2:2,1/8,1/16,3.5000,3.7500,[v3,staff1])-note(n37,56,106450,109590,36,1,0). +snote(n39,[B,n],3,2:2,3/16,1/16,3.7500,4.0000,[v3,staff1])-note(n39,59,109910,113780,43,1,0). +snote(n40,[B,n],2,2:2,3/16,1/16,3.7500,4.0000,[v4,staff2])-note(n38,47,109860,113630,25,1,0). +snote(n41,[G,#],3,3:1,0,1/16,4.0000,4.2500,[v3,staff1])-note(n41,56,113700,115950,41,1,0). +snote(n42,[E,n],2,3:1,0,1/4,4.0000,5.0000,[v7,staff2])-note(n40,40,113580,125350,27,1,0). +snote(n43,[A,n],4,3:1,1/16,1/16,4.2500,4.5000,[v1,staff1])-note(n42,69,117340,119200,57,1,0). +snote(n44,[E,n],4,3:1,1/16,1/16,4.2500,4.5000,[v3,staff1])-note(n44,64,117670,118780,42,1,0). +snote(n45,[B,n],2,3:1,1/16,1/8,4.2500,4.7500,[v4,staff2,accent])-note(n43,47,117510,119720,33,1,0). +snote(n46,[A,n],4,3:1,1/8,1/16,4.5000,4.7500,[v1,staff1])-note(n45,69,121000,124990,68,1,0). +snote(n47,[B,n],3,3:1,1/8,1/16,4.5000,4.7500,[v3,staff1])-note(n46,59,121410,123650,44,1,0). +snote(n48,[G,#],4,3:1,3/16,1/16,4.7500,5.0000,[v1,staff1])-note(n47,68,124670,126260,72,1,0). +snote(n49,[E,n],4,3:1,3/16,1/16,4.7500,5.0000,[v3,staff1])-note(n48,64,124960,125340,45,1,0). +snote(n50,[B,n],2,3:1,3/16,1/16,4.7500,5.0000,[v4,staff2])-note(n49,47,124970,127130,34,1,0). +snote(n51,[C,#],5,3:2,0,3/16,5.0000,5.7500,[v1,staff1])-note(n50,73,129250,135540,71,1,0). +snote(n52,[D,#],4,3:2,0,1/16,5.0000,5.2500,[v3,staff1])-note(n51,63,129490,135340,51,1,0). +snote(n53,[B,n],1,3:2,0,1/4,5.0000,6.0000,[v7,staff2])-note(n52,35,129550,142060,36,1,0). +snote(n54,[A,n],4,3:2,1/16,1/16,5.2500,5.5000,[v3,staff1])-note(n54,69,134040,135850,56,1,0). +snote(n55,[B,n],2,3:2,1/16,1/8,5.2500,5.7500,[v4,staff2,accent])-note(n53,47,133890,136630,34,1,0). +snote(n56,[B,n],3,3:2,1/8,1/16,5.5000,5.7500,[v3,staff1])-note(n55,59,137780,140480,49,1,0). +snote(n57,[B,n],4,3:2,3/16,1/16,5.7500,6.0000,[v1,staff1])-note(n56,71,141230,142200,60,1,0). +snote(n58,[D,#],4,3:2,3/16,1/16,5.7500,6.0000,[v3,staff1])-note(n57,63,141470,142220,44,1,0). +snote(n59,[B,n],2,3:2,3/16,1/16,5.7500,6.0000,[v4,staff2])-note(n58,47,141540,143270,34,1,0). +snote(n60,[A,n],4,4:1,0,1/16,6.0000,6.2500,[v1,staff1])-note(n59,69,145510,150400,59,1,0). +snote(n61,[B,n],3,4:1,0,1/16,6.0000,6.2500,[v3,staff1])-note(n61,59,145780,149340,42,1,0). +snote(n62,[E,n],2,4:1,0,1/4,6.0000,7.0000,[v7,staff2])-note(n60,40,145610,158320,30,1,0). +snote(n63,[G,#],4,4:1,1/16,1/16,6.2500,6.5000,[v1,staff1])-note(n62,68,149580,151760,52,1,0). +snote(n64,[E,n],4,4:1,1/16,1/16,6.2500,6.5000,[v3,staff1])-note(n63,64,149730,150890,36,1,0). +snote(n65,[B,n],2,4:1,1/16,1/8,6.2500,6.7500,[v4,staff2,accent])-note(n64,47,149840,152800,11,1,0). +snote(n66,[D,#],4,4:1,1/8,1/16,6.5000,6.7500,[v1,staff1])-note(n65,63,153100,156670,53,1,0). +snote(n67,[G,#],3,4:1,1/8,1/16,6.5000,6.7500,[v3,staff1])-note(n66,56,153490,155610,39,1,0). +snote(n68,[E,n],4,4:1,3/16,1/16,6.7500,7.0000,[v1,staff1])-note(n67,64,156810,161700,55,1,0). +snote(n69,[B,n],3,4:1,3/16,1/16,6.7500,7.0000,[v3,staff1])-note(n68,59,156950,158350,39,1,0). +snote(n70,[B,n],2,4:1,3/16,1/16,6.7500,7.0000,[v4,staff2])-note(n69,47,157040,160560,29,1,0). +snote(n71,[F,#],4,4:2,0,5/16,7.0000,8.2500,[v1,staff1,accent])-note(n70,66,161500,179640,54,1,0). +snote(n72,[C,#],4,4:2,0,1/16,7.0000,7.2500,[v2,staff1])-note(n72,61,161960,167190,34,1,0). +snote(n73,[A,n],3,4:2,0,1/16,7.0000,7.2500,[v3,staff1])-note(n73,57,162010,164240,27,1,0). +snote(n74,[B,n],1,4:2,0,1/4,7.0000,8.0000,[v7,staff2])-note(n71,35,161730,175760,25,1,0). +snote(n75,[D,#],4,4:2,1/16,1/16,7.2500,7.5000,[v2,staff1])-note(n74,63,167060,170840,41,1,0). +snote(n76,[B,n],3,4:2,1/16,1/16,7.2500,7.5000,[v3,staff1])-note(n76,59,167650,169160,17,1,0). +snote(n77,[B,n],2,4:2,1/16,1/8,7.2500,7.7500,[v4,staff2,accent])-note(n75,47,167200,171160,29,1,0). +snote(n78,[C,#],4,4:2,1/8,1/16,7.5000,7.7500,[v2,staff1])-note(n77,61,170980,174810,42,1,0). +snote(n79,[A,n],3,4:2,1/8,1/16,7.5000,7.7500,[v3,staff1])-note(n78,57,171320,171940,30,1,0). +snote(n80,[D,#],4,4:2,3/16,1/16,7.7500,8.0000,[v2,staff1])-note(n80,63,174670,178160,41,1,0). +snote(n81,[B,n],3,4:2,3/16,1/16,7.7500,8.0000,[v3,staff1])-note(n81,59,174920,175730,26,1,0). +snote(n82,[B,n],2,4:2,3/16,1/16,7.7500,8.0000,[v4,staff2])-note(n79,47,174620,178460,34,1,0). +snote(n83,[C,#],4,5:1,0,1/16,8.0000,8.2500,[v2,staff1])-note(n83,61,178350,182380,38,1,0). +snote(n84,[A,n],3,5:1,0,1/16,8.0000,8.2500,[v3,staff1])-note(n84,57,178530,179500,31,1,0). +snote(n85,[B,n],1,5:1,0,1/4,8.0000,9.0000,[v7,staff2])-note(n82,35,178340,191130,31,1,0). +snote(n86,[G,#],4,5:1,1/16,1/16,8.2500,8.5000,[v1,staff1])-note(n85,68,182560,185450,55,1,0). +snote(n87,[D,#],4,5:1,1/16,1/16,8.2500,8.5000,[v2,staff1])-note(n86,63,182660,185840,40,1,0). +snote(n88,[B,n],3,5:1,1/16,1/16,8.2500,8.5000,[v3,staff1])-note(n88,59,182830,184310,34,1,0). +snote(n89,[B,n],2,5:1,1/16,1/8,8.2500,8.7500,[v4,staff2,accent])-note(n87,47,182770,186610,31,1,0). +snote(n90,[G,#],4,5:1,1/8,1/16,8.5000,8.7500,[v1,staff1])-note(n89,68,186310,189250,60,1,0). +snote(n91,[C,#],4,5:1,1/8,1/16,8.5000,8.7500,[v2,staff1])-note(n90,61,186340,188090,46,1,0). +snote(n92,[A,n],3,5:1,1/8,1/16,8.5000,8.7500,[v3,staff1])-note(n91,57,186490,187120,38,1,0). +snote(n93,[F,#],4,5:1,3/16,1/16,8.7500,9.0000,[v1,staff1])-note(n92,66,190020,191400,57,1,0). +snote(n94,[D,#],4,5:1,3/16,1/16,8.7500,9.0000,[v2,staff1])-note(n94,63,190240,191150,45,1,0). +snote(n95,[B,n],3,5:1,3/16,1/16,8.7500,9.0000,[v3,staff1])-note(n93,59,190200,190650,42,1,0). +snote(n96,[B,n],2,5:1,3/16,1/16,8.7500,9.0000,[v4,staff2])-note(n95,47,190300,194130,37,1,0). +snote(n97,[E,n],4,5:2,0,1/4,9.0000,10.0000,[v1,staff1])-note(n96,64,194370,207290,57,1,0). +snote(n98,[G,#],3,5:2,0,1/16,9.0000,9.2500,[v3,staff1])-note(n98,56,194890,198940,25,1,0). +snote(n99,[E,n],2,5:2,0,1/4,9.0000,10.0000,[v7,staff2])-note(n97,40,194550,205150,28,1,0). +snote(n100,[B,n],3,5:2,1/16,1/16,9.2500,9.5000,[v3,staff1])-note(n100,59,198750,202960,37,1,0). +snote(n101,[B,n],2,5:2,1/16,1/8,9.2500,9.7500,[v4,staff2,accent])-note(n99,47,198730,201830,23,1,0). +snote(n102,[G,#],3,5:2,1/8,1/16,9.5000,9.7500,[v3,staff1])-note(n101,56,202330,204330,36,1,0). +snote(n103,[B,n],3,5:2,3/16,1/16,9.7500,10.0000,[v3,staff1])-note(n102,59,206520,207010,28,1,0). +snote(n104,[B,n],2,5:2,3/16,1/16,9.7500,10.0000,[v4,staff2])-note(n103,47,206530,207340,26,1,0). +snote(n105,[G,#],4,6:1,0,1/16,10.0000,10.2500,[v1,staff1])-note(n104,68,211380,215630,46,1,0). +snote(n106,[D,n],4,6:1,0,1/16,10.0000,10.2500,[v3,staff1])-note(n106,62,211610,215430,32,1,0). +snote(n107,[E,n],2,6:1,0,1/4,10.0000,11.0000,[v7,staff2])-note(n105,40,211410,224770,28,1,0). +snote(n108,[A,n],4,6:1,1/16,1/16,10.2500,10.5000,[v1,staff1])-note(n107,69,215240,218910,50,1,0). +snote(n109,[E,n],4,6:1,1/16,1/16,10.2500,10.5000,[v3,staff1])-note(n109,64,215540,218500,40,1,0). +snote(n110,[E,n],3,6:1,1/16,1/8,10.2500,10.7500,[v4,staff2,accent])-note(n108,52,215300,218960,35,1,0). +snote(n111,[F,#],4,6:1,1/8,1/16,10.5000,10.7500,[v1,staff1])-note(n110,66,218480,222550,55,1,0). +snote(n112,[D,n],4,6:1,1/8,1/16,10.5000,10.7500,[v3,staff1])-note(n111,62,218800,220920,40,1,0). +snote(n113,[G,#],4,6:1,3/16,1/16,10.7500,11.0000,[v1,staff1])-note(n112,68,221900,223760,62,1,0). +snote(n114,[E,n],4,6:1,3/16,1/16,10.7500,11.0000,[v3,staff1])-note(n114,64,222250,222720,31,1,0). +snote(n115,[E,n],3,6:1,3/16,1/16,10.7500,11.0000,[v4,staff2])-note(n113,52,221960,224830,39,1,0). +snote(n116,[A,n],4,6:2,0,1/16,11.0000,11.2500,[v1,staff1])-note(n115,69,225330,229050,68,1,0). +snote(n117,[C,#],4,6:2,0,1/16,11.0000,11.2500,[v3,staff1])-note(n116,61,225620,229230,46,1,0). +snote(n118,[A,n],2,6:2,0,1/4,11.0000,12.0000,[v7,staff2])-note(n117,45,225640,235050,34,1,0). +snote(n119,[B,n],4,6:2,1/16,1/16,11.2500,11.5000,[v1,staff1])-note(n118,71,228650,231870,70,1,0). +snote(n120,[E,n],4,6:2,1/16,1/16,11.2500,11.5000,[v3,staff1])-note(n120,64,228850,232150,57,1,0). +snote(n121,[E,n],3,6:2,1/16,1/8,11.2500,11.7500,[v4,staff2,accent])-note(n119,52,228720,231850,40,1,0). +snote(n122,[G,#],4,6:2,1/8,1/16,11.5000,11.7500,[v1,staff1])-note(n121,68,231860,235880,68,1,0). +snote(n123,[C,#],4,6:2,1/8,1/16,11.5000,11.7500,[v3,staff1])-note(n122,61,231950,235040,61,1,0). +snote(n124,[A,n],4,6:2,3/16,1/16,11.7500,12.0000,[v1,staff1])-note(n123,69,235160,239480,72,1,0). +snote(n125,[E,n],4,6:2,3/16,1/16,11.7500,12.0000,[v3,staff1])-note(n125,64,235380,237030,47,1,0). +snote(n126,[E,n],3,6:2,3/16,1/16,11.7500,12.0000,[v4,staff2])-note(n124,52,235300,239290,40,1,0). +snote(n127,[C,#],5,7:1,0,1/8,12.0000,12.5000,[v1,staff1])-note(n127,73,239070,246000,71,1,0). +snote(n128,[C,#],4,7:1,0,1/16,12.0000,12.2500,[v3,staff1])-note(n128,61,239370,244770,45,1,0). +snote(n129,[A,n],2,7:1,0,1/4,12.0000,13.0000,[v7,staff2])-note(n126,45,238980,253980,38,1,0). +snote(n130,[E,n],4,7:1,1/16,1/16,12.2500,12.5000,[v3,staff1])-note(n130,64,243620,247380,48,1,0). +snote(n131,[F,#],3,7:1,1/16,1/8,12.2500,12.7500,[v4,staff2,accent])-note(n129,54,243410,247960,55,1,0). +snote(n132,[F,#],4,7:1,1/8,1/4,12.5000,13.5000,[v1,staff1])-note(n131,66,247010,257510,64,1,0). +snote(n133,[C,#],4,7:1,1/8,1/16,12.5000,12.7500,[v3,staff1])-note(n132,61,247540,250620,34,1,0). +snote(n134,[E,n],4,7:1,3/16,1/16,12.7500,13.0000,[v3,staff1])-note(n133,64,250460,254090,53,1,0). +snote(n135,[F,#],3,7:1,3/16,1/16,12.7500,13.0000,[v4,staff2])-note(n134,54,250630,253910,39,1,0). +snote(n136,[B,n],3,7:2,0,1/16,13.0000,13.2500,[v3,staff1])-note(n135,59,253690,257020,55,1,0). +snote(n137,[B,n],2,7:2,0,1/4,13.0000,14.0000,[v7,staff2])-note(n136,47,253760,267650,48,1,0). +snote(n138,[E,n],4,7:2,1/16,1/16,13.2500,13.5000,[v3,staff1])-note(n138,64,257030,258360,59,1,0). +snote(n139,[F,#],3,7:2,1/16,1/8,13.2500,13.7500,[v4,staff2,accent])-note(n137,54,256860,258730,40,1,0). +snote(n140,[A,#],4,7:2,1/8,0,13.5000,13.5000,[v1,staff1])-note(n140,70,259690,261790,63,1,0). +snote(n141,[G,#],4,7:2,1/8,1/16,13.5000,13.7500,[v1,staff1])-note(n141,68,260190,264370,60,1,0). +snote(n142,[B,n],3,7:2,1/8,1/16,13.5000,13.7500,[v3,staff1])-note(n139,59,259100,261360,45,1,0). +snote(n143,[F,#],4,7:2,3/16,3/16,13.7500,14.5000,[v1,staff1])-note(n142,66,263410,271400,61,1,0). +snote(n144,[E,n],4,7:2,3/16,1/16,13.7500,14.0000,[v3,staff1])-note(n144,64,263670,267660,53,1,0). +snote(n145,[F,#],3,7:2,3/16,1/16,13.7500,14.0000,[v4,staff2])-note(n143,54,263630,267490,39,1,0). +snote(n146,[A,#],3,8:1,0,1/16,14.0000,14.2500,[v3,staff1])-note(n146,58,267180,271360,56,1,0). +snote(n147,[C,#],3,8:1,0,1/4,14.0000,15.0000,[v7,staff2])-note(n145,49,267140,279190,49,1,0). +snote(n148,[E,n],4,8:1,1/16,1/16,14.2500,14.5000,[v3,staff1])-note(n147,64,270530,272240,58,1,0). +snote(n149,[F,#],3,8:1,1/16,1/8,14.2500,14.7500,[v4,staff2,accent])-note(n148,54,270650,272610,37,1,0). +snote(n150,[A,#],4,8:1,1/8,0,14.5000,14.5000,[v1,staff1])-note(n150,70,273350,275320,61,1,0). +snote(n151,[G,#],4,8:1,1/8,1/16,14.5000,14.7500,[v1,staff1])-note(n151,68,273970,278200,65,1,0). +snote(n152,[A,#],3,8:1,1/8,1/16,14.5000,14.7500,[v3,staff1])-note(n149,58,272740,277790,52,1,0). +snote(n153,[F,#],4,8:1,3/16,1/16,14.7500,15.0000,[v1,staff1])-note(n152,66,277740,280410,55,1,0). +snote(n154,[E,n],4,8:1,3/16,1/16,14.7500,15.0000,[v3,staff1])-note(n153,64,277880,278940,49,1,0). +snote(n155,[F,#],3,8:1,3/16,1/16,14.7500,15.0000,[v4,staff2])-note(n154,54,277980,279410,42,1,0). +snote(n156,[B,n],4,8:2,0,1/4,15.0000,16.0000,[v1,staff1])-note(n155,71,282610,289660,53,1,0). +snote(n157,[D,#],4,8:2,0,1/4,15.0000,16.0000,[v2,staff1])-note(n156,63,282840,287690,38,1,0). +snote(n158,[A,n],3,8:2,0,1/16,15.0000,15.2500,[v3,staff1])-note(n158,57,282950,286630,32,1,0). +snote(n159,[B,n],2,8:2,0,1/4,15.0000,16.0000,[v7,staff2])-note(n157,47,282910,293370,30,1,0). +snote(n160,[B,n],3,8:2,1/16,1/16,15.2500,15.5000,[v3,staff1])-note(n159,59,287990,290090,37,1,0). +snote(n161,[F,#],3,8:2,1/16,1/8,15.2500,15.7500,[v4,staff2,accent])-note(n160,54,288150,292690,30,1,0). +snote(n162,[A,n],3,8:2,1/8,1/16,15.5000,15.7500,[v3,staff1])-note(n161,57,291830,292750,46,1,0). +snote(n163,[B,n],3,8:2,3/16,1/16,15.7500,16.0000,[v3,staff1])-note(n162,59,296380,297650,44,1,0). +snote(n164,[F,#],3,8:2,3/16,1/16,15.7500,16.0000,[v4,staff2])-note(n163,54,296790,297670,12,1,0). +snote(n165,[G,#],4,9:1,0,1/8,16.0000,16.5000,[v1,staff1])-note(n165,68,302750,311510,45,1,0). +snote(n166,[E,n],4,9:1,0,1/8,16.0000,16.5000,[v2,staff1])-note(n167,64,303110,310790,38,1,0). +snote(n167,[G,#],3,9:1,0,1/16,16.0000,16.2500,[v3,staff1])-note(n166,56,303030,308370,26,1,0). +snote(n168,[E,n],2,9:1,0,1/4,16.0000,17.0000,[v7,staff2])-note(n164,40,302750,315600,25,1,0). +snote(n169,[B,n],3,9:1,1/16,1/16,16.2500,16.5000,[v3,staff1])-note(n169,59,308270,312220,35,1,0). +snote(n170,[B,n],2,9:1,1/16,1/8,16.2500,16.7500,[v4,staff2,accent])-note(n168,47,308170,311530,24,1,0). +snote(n171,[D,#],4,9:1,1/8,1/16,16.5000,16.7500,[v1,staff1])-note(n170,63,312000,315750,48,1,0). +snote(n172,[G,#],3,9:1,1/8,1/16,16.5000,16.7500,[v3,staff1])-note(n171,56,312360,314740,30,1,0). +snote(n173,[E,n],4,9:1,3/16,1/16,16.7500,17.0000,[v1,staff1])-note(n172,64,315690,319860,53,1,0). +snote(n174,[B,n],3,9:1,3/16,1/16,16.7500,17.0000,[v3,staff1])-note(n174,59,316020,320320,33,1,0). +snote(n175,[B,n],2,9:1,3/16,1/16,16.7500,17.0000,[v4,staff2])-note(n173,47,315750,319890,33,1,0). +snote(n176,[F,#],4,9:2,0,5/16,17.0000,18.2500,[v1,staff1])-note(n175,66,319980,337300,47,1,0). +snote(n177,[D,#],4,9:2,0,3/8,17.0000,18.5000,[v2,staff1])-note(n177,63,320220,340720,37,1,0). +snote(n178,[A,n],3,9:2,0,1/16,17.0000,17.2500,[v3,staff1])-note(n178,57,320270,324990,33,1,0). +snote(n179,[B,n],1,9:2,0,1/4,17.0000,18.0000,[v7,staff2])-note(n176,35,320020,331090,30,1,0). +snote(n180,[B,n],3,9:2,1/16,1/16,17.2500,17.5000,[v3,staff1])-note(n180,59,324970,328860,37,1,0). +snote(n181,[B,n],2,9:2,1/16,1/8,17.2500,17.7500,[v4,staff2,accent])-note(n179,47,324830,328830,33,1,0). +snote(n182,[A,n],3,9:2,1/8,1/16,17.5000,17.7500,[v3,staff1])-note(n181,57,328450,332220,36,1,0). +snote(n183,[B,n],3,9:2,3/16,1/16,17.7500,18.0000,[v3,staff1])-note(n182,59,332200,335920,40,1,0). +snote(n184,[B,n],2,9:2,3/16,1/16,17.7500,18.0000,[v4,staff2])-note(n183,47,332310,335740,27,1,0). +snote(n185,[A,n],3,10:1,0,1/16,18.0000,18.2500,[v3,staff1])-note(n184,57,335880,339910,38,1,0). +snote(n186,[B,n],1,10:1,0,1/4,18.0000,19.0000,[v7,staff2])-note(n185,35,336030,349380,25,1,0). +snote(n187,[G,#],4,10:1,1/16,1/16,18.2500,18.5000,[v1,staff1])-note(n186,68,339850,342070,51,1,0). +snote(n188,[B,n],3,10:1,1/16,1/16,18.2500,18.5000,[v3,staff1])-note(n187,59,339860,341790,44,1,0). +snote(n189,[B,n],2,10:1,1/16,1/8,18.2500,18.7500,[v4,staff2,accent])-note(n188,47,339970,342970,36,1,0). +snote(n190,[G,#],4,10:1,1/8,1/16,18.5000,18.7500,[v1,staff1])-note(n189,68,343540,346420,56,1,0). +snote(n191,[D,#],4,10:1,1/8,1/8,18.5000,19.0000,[v2,staff1])-note(n190,63,343630,347830,45,1,0). +snote(n192,[A,n],3,10:1,1/8,1/16,18.5000,18.7500,[v3,staff1])-note(n191,57,343850,345830,30,1,0). +snote(n193,[F,#],4,10:1,3/16,1/16,18.7500,19.0000,[v1,staff1])-note(n192,66,347170,348880,55,1,0). +snote(n194,[B,n],3,10:1,3/16,1/16,18.7500,19.0000,[v3,staff1])-note(n194,59,347480,347890,37,1,0). +snote(n195,[B,n],2,10:1,3/16,1/16,18.7500,19.0000,[v4,staff2])-note(n193,47,347360,350150,32,1,0). +snote(n196,[G,#],4,10:2,0,5/16,19.0000,20.2500,[v1,staff1])-note(n195,68,351770,369660,52,1,0). +snote(n197,[E,n],4,10:2,0,1/4,19.0000,20.0000,[v2,staff1])-note(n197,64,352010,368560,43,1,0). +snote(n198,[G,#],3,10:2,0,1/16,19.0000,19.2500,[v3,staff1])-note(n198,56,352150,356980,24,1,0). +snote(n199,[E,n],2,10:2,0,1/4,19.0000,20.0000,[v7,staff2])-note(n196,40,351950,364240,24,1,0). +snote(n200,[B,n],3,10:2,1/16,1/16,19.2500,19.5000,[v3,staff1])-note(n200,59,356960,361320,35,1,0). +snote(n201,[B,n],2,10:2,1/16,1/8,19.2500,19.7500,[v4,staff2,accent])-note(n199,47,356860,360570,24,1,0). +snote(n202,[G,#],3,10:2,1/8,1/16,19.5000,19.7500,[v3,staff1])-note(n201,56,360550,363900,32,1,0). +snote(n203,[B,n],3,10:2,3/16,1/16,19.7500,20.0000,[v3,staff1])-note(n203,59,364130,367640,39,1,0). +snote(n204,[B,n],2,10:2,3/16,1/16,19.7500,20.0000,[v4,staff2])-note(n202,47,364100,367700,28,1,0). +snote(n205,[G,#],3,11:1,0,1/16,20.0000,20.2500,[v3,staff1])-note(n204,56,367850,370540,43,1,0). +snote(n206,[E,n],2,11:1,0,1/4,20.0000,21.0000,[v7,staff2])-note(n205,40,367870,379290,28,1,0). +snote(n207,[A,n],4,11:1,1/16,1/16,20.2500,20.5000,[v1,staff1])-note(n206,69,371480,373510,54,1,0). +snote(n208,[E,n],4,11:1,1/16,1/16,20.2500,20.5000,[v3,staff1])-note(n208,64,371860,373290,41,1,0). +snote(n209,[B,n],2,11:1,1/16,1/8,20.2500,20.7500,[v4,staff2,accent])-note(n207,47,371800,373960,31,1,0). +snote(n210,[A,n],4,11:1,1/8,1/16,20.5000,20.7500,[v1,staff1])-note(n209,69,375170,379120,65,1,0). +snote(n211,[B,n],3,11:1,1/8,1/16,20.5000,20.7500,[v3,staff1])-note(n210,59,375650,377960,39,1,0). +snote(n212,[G,#],4,11:1,3/16,1/16,20.7500,21.0000,[v1,staff1])-note(n211,68,378760,380320,67,1,0). +snote(n213,[E,n],4,11:1,3/16,1/16,20.7500,21.0000,[v3,staff1])-note(n213,64,379150,379470,35,1,0). +snote(n214,[B,n],2,11:1,3/16,1/16,20.7500,21.0000,[v4,staff2])-note(n212,47,379070,382480,35,1,0). +snote(n215,[C,#],5,11:2,0,3/16,21.0000,21.7500,[v1,staff1])-note(n214,73,383250,389340,71,1,0). +snote(n216,[D,#],4,11:2,0,1/16,21.0000,21.2500,[v3,staff1])-note(n216,63,383540,389220,47,1,0). +snote(n217,[B,n],1,11:2,0,1/4,21.0000,22.0000,[v7,staff2])-note(n215,35,383500,397030,34,1,0). +snote(n218,[A,n],4,11:2,1/16,1/16,21.2500,21.5000,[v3,staff1])-note(n218,69,387970,389590,59,1,0). +snote(n219,[B,n],2,11:2,1/16,1/8,21.2500,21.7500,[v4,staff2,accent])-note(n217,47,387860,391610,33,1,0). +snote(n220,[B,n],3,11:2,1/8,1/16,21.5000,21.7500,[v3,staff1])-note(n219,59,391570,393950,55,1,0). +snote(n221,[B,n],4,11:2,3/16,1/16,21.7500,22.0000,[v1,staff1])-note(n220,71,395060,396060,63,1,0). +snote(n222,[D,#],4,11:2,3/16,1/16,21.7500,22.0000,[v3,staff1])-note(n222,63,395320,395910,40,1,0). +snote(n223,[B,n],2,11:2,3/16,1/16,21.7500,22.0000,[v4,staff2])-note(n221,47,395200,398730,37,1,0). +snote(n224,[A,n],4,12:1,0,1/16,22.0000,22.2500,[v1,staff1])-note(n223,69,399130,403990,56,1,0). +snote(n225,[B,n],3,12:1,0,1/16,22.0000,22.2500,[v3,staff1])-note(n225,59,399500,402590,39,1,0). +snote(n226,[E,n],2,12:1,0,1/4,22.0000,23.0000,[v7,staff2])-note(n224,40,399180,411860,32,1,0). +snote(n227,[G,#],4,12:1,1/16,1/16,22.2500,22.5000,[v1,staff1])-note(n226,68,403070,405340,50,1,0). +snote(n228,[E,n],4,12:1,1/16,1/16,22.2500,22.5000,[v3,staff1])-note(n228,64,403220,404220,38,1,0). +snote(n229,[B,n],2,12:1,1/16,1/8,22.2500,22.7500,[v4,staff2,accent])-note(n227,47,403180,406490,20,1,0). +snote(n230,[D,#],4,12:1,1/8,1/16,22.5000,22.7500,[v1,staff1])-note(n229,63,406660,409660,49,1,0). +snote(n231,[G,#],3,12:1,1/8,1/16,22.5000,22.7500,[v3,staff1])-note(n230,56,407010,409020,37,1,0). +snote(n232,[E,n],4,12:1,3/16,1/16,22.7500,23.0000,[v1,staff1])-note(n232,64,410460,415280,58,1,0). +snote(n233,[B,n],3,12:1,3/16,1/16,22.7500,23.0000,[v3,staff1])-note(n233,59,410630,410990,36,1,0). +snote(n234,[B,n],2,12:1,3/16,1/16,22.7500,23.0000,[v4,staff2])-note(n231,47,410450,413500,32,1,0). +snote(n235,[F,#],4,12:2,0,5/16,23.0000,24.2500,[v1,staff1])-note(n234,66,415140,432370,55,1,0). +snote(n236,[C,#],4,12:2,0,1/16,23.0000,23.2500,[v2,staff1])-note(n237,61,415610,419990,32,1,0). +snote(n237,[A,n],3,12:2,0,1/16,23.0000,23.2500,[v3,staff1])-note(n236,57,415510,417610,31,1,0). +snote(n238,[B,n],1,12:2,0,1/4,23.0000,24.0000,[v7,staff2])-note(n235,35,415510,427820,26,1,0). +snote(n239,[D,#],4,12:2,1/16,1/16,23.2500,23.5000,[v2,staff1])-note(n238,63,419940,423640,40,1,0). +snote(n240,[B,n],3,12:2,1/16,1/16,23.2500,23.5000,[v3,staff1])-note(n240,59,420340,421670,29,1,0). +snote(n241,[B,n],2,12:2,1/16,1/8,23.2500,23.7500,[v4,staff2,accent])-note(n239,47,420200,424390,27,1,0). +snote(n242,[C,#],4,12:2,1/8,1/16,23.5000,23.7500,[v2,staff1])-note(n241,61,423600,427480,44,1,0). +snote(n243,[A,n],3,12:2,1/8,1/16,23.5000,23.7500,[v3,staff1])-note(n242,57,423890,424800,34,1,0). +snote(n244,[D,#],4,12:2,3/16,1/16,23.7500,24.0000,[v2,staff1])-note(n243,63,427410,431500,49,1,0). +snote(n245,[B,n],3,12:2,3/16,1/16,23.7500,24.0000,[v3,staff1])-note(n245,59,427740,428760,24,1,0). +snote(n246,[B,n],2,12:2,3/16,1/16,23.7500,24.0000,[v4,staff2])-note(n244,47,427430,431060,34,1,0). +snote(n247,[C,#],4,13:1,0,1/16,24.0000,24.2500,[v2,staff1])-note(n246,61,431030,435080,49,1,0). +snote(n248,[A,n],3,13:1,0,1/16,24.0000,24.2500,[v3,staff1])-note(n248,57,431150,432820,40,1,0). +snote(n249,[B,n],1,13:1,0,1/4,24.0000,25.0000,[v7,staff2])-note(n247,35,431110,443880,28,1,0). +snote(n250,[G,#],4,13:1,1/16,1/16,24.2500,24.5000,[v1,staff1])-note(n249,68,434940,437570,62,1,0). +snote(n251,[D,#],4,13:1,1/16,1/16,24.2500,24.5000,[v2,staff1])-note(n252,63,435310,437110,30,1,0). +snote(n252,[B,n],3,13:1,1/16,1/16,24.2500,24.5000,[v3,staff1])-note(n251,59,435270,436450,34,1,0). +snote(n253,[B,n],2,13:1,1/16,1/8,24.2500,24.7500,[v4,staff2,accent])-note(n250,47,435220,438320,31,1,0). +snote(n254,[G,#],4,13:1,1/8,1/16,24.5000,24.7500,[v1,staff1])-note(n253,68,438560,441880,63,1,0). +snote(n255,[C,#],4,13:1,1/8,1/16,24.5000,24.7500,[v2,staff1])-note(n254,61,438850,441160,45,1,0). +snote(n256,[A,n],3,13:1,1/8,1/16,24.5000,24.7500,[v3,staff1])-note(n255,57,438970,439590,22,1,0). +snote(n257,[F,#],4,13:1,3/16,1/16,24.7500,25.0000,[v1,staff1])-note(n256,66,442240,443790,59,1,0). +snote(n258,[D,#],4,13:1,3/16,1/16,24.7500,25.0000,[v2,staff1])-note(n259,63,442440,443200,44,1,0). +snote(n259,[B,n],3,13:1,3/16,1/16,24.7500,25.0000,[v3,staff1])-note(n258,59,442420,442900,45,1,0). +snote(n260,[B,n],2,13:1,3/16,1/16,24.7500,25.0000,[v4,staff2])-note(n257,47,442380,445390,39,1,0). +snote(n261,[E,n],4,13:2,0,1/4,25.0000,26.0000,[v1,staff1])-note(n260,64,446780,459350,59,1,0). +snote(n262,[G,#],3,13:2,0,1/16,25.0000,25.2500,[v3,staff1])-note(n262,56,447280,451210,29,1,0). +snote(n263,[E,n],2,13:2,0,1/4,25.0000,26.0000,[v7,staff2])-note(n261,40,447140,456720,26,1,0). +snote(n264,[B,n],3,13:2,1/16,1/16,25.2500,25.5000,[v3,staff1])-note(n263,59,450860,454770,40,1,0). +snote(n265,[B,n],2,13:2,1/16,1/8,25.2500,25.7500,[v4,staff2,accent])-note(n264,47,450870,453570,24,1,0). +snote(n266,[G,#],3,13:2,1/8,1/16,25.5000,25.7500,[v3,staff1])-note(n265,56,454420,455480,38,1,0). +snote(n267,[B,n],3,13:2,3/16,1/16,25.7500,26.0000,[v3,staff1])-note(n266,59,458520,459050,31,1,0). +snote(n268,[B,n],2,13:2,3/16,1/16,25.7500,26.0000,[v4,staff2])-note(n267,47,458590,459390,27,1,0). +snote(n269,[B,n],4,14:1,0,1/16,26.0000,26.2500,[v1,staff1])-note(n268,71,463190,465830,53,1,0). +snote(n270,[F,#],4,14:1,0,1/16,26.0000,26.2500,[v2,staff1])-note(n270,66,463410,466730,31,1,0). +snote(n271,[D,n],4,14:1,0,1/16,26.0000,26.2500,[v3,staff1])-note(n271,62,463580,465880,31,1,0). +snote(n272,[E,n],2,14:1,0,1/4,26.0000,27.0000,[v7,staff2])-note(n269,40,463350,476010,31,1,0). +snote(n273,[C,#],5,14:1,1/16,1/16,26.2500,26.5000,[v1,staff1])-note(n272,73,467690,469920,56,1,0). +snote(n274,[G,#],4,14:1,1/16,1/16,26.2500,26.5000,[v2,staff1])-note(n274,68,467920,469900,43,1,0). +snote(n275,[E,n],4,14:1,1/16,1/16,26.2500,26.5000,[v3,staff1])-note(n275,64,468100,468990,38,1,0). +snote(n276,[E,n],3,14:1,1/16,1/8,26.2500,26.7500,[v4,staff2,accent])-note(n273,52,467810,470520,34,1,0). +snote(n277,[C,#],5,14:1,1/8,1/16,26.5000,26.7500,[v1,staff1])-note(n276,73,471080,473310,63,1,0). +snote(n278,[F,#],4,14:1,1/8,1/16,26.5000,26.7500,[v2,staff1])-note(n277,66,471310,473710,47,1,0). +snote(n279,[D,n],4,14:1,1/8,1/16,26.5000,26.7500,[v3,staff1])-note(n278,62,471340,472280,44,1,0). +snote(n280,[B,n],4,14:1,3/16,1/16,26.7500,27.0000,[v1,staff1])-note(n279,71,474630,475960,64,1,0). +snote(n281,[G,#],4,14:1,3/16,1/16,26.7500,27.0000,[v2,staff1])-note(n281,68,474820,476290,46,1,0). +snote(n282,[E,n],4,14:1,3/16,1/16,26.7500,27.0000,[v3,staff1])-note(n282,64,474850,475130,37,1,0). +snote(n283,[E,n],3,14:1,3/16,1/16,26.7500,27.0000,[v4,staff2])-note(n280,52,474710,477230,42,1,0). +snote(n284,[A,n],4,14:2,0,1/16,27.0000,27.2500,[v1,staff1])-note(n283,69,478030,481940,62,1,0). +snote(n285,[C,#],4,14:2,0,1/16,27.0000,27.2500,[v3,staff1])-note(n285,61,478280,481560,44,1,0). +snote(n286,[A,n],2,14:2,0,1/4,27.0000,28.0000,[v7,staff2])-note(n284,45,478150,487490,39,1,0). +snote(n287,[B,n],4,14:2,1/16,1/16,27.2500,27.5000,[v1,staff1])-note(n287,71,481540,484830,67,1,0). +snote(n288,[E,n],4,14:2,1/16,1/16,27.2500,27.5000,[v3,staff1])-note(n288,64,481680,484470,54,1,0). +snote(n289,[E,n],3,14:2,1/16,1/8,27.2500,27.7500,[v4,staff2,accent])-note(n286,52,481470,484090,39,1,0). +snote(n290,[G,#],4,14:2,1/8,1/16,27.5000,27.7500,[v1,staff1])-note(n289,68,484610,488020,65,1,0). +snote(n291,[C,#],4,14:2,1/8,1/16,27.5000,27.7500,[v3,staff1])-note(n290,61,484810,485690,56,1,0). +snote(n292,[A,n],4,14:2,3/16,1/16,27.7500,28.0000,[v1,staff1])-note(n291,69,487970,488910,69,1,0). +snote(n293,[E,n],4,14:2,3/16,1/16,27.7500,28.0000,[v3,staff1])-note(n293,64,488090,488420,56,1,0). +snote(n294,[E,n],3,14:2,3/16,1/16,27.7500,28.0000,[v4,staff2])-note(n292,52,487990,488850,48,1,0). +snote(n295,[D,#],5,15:1,0,1/16,28.0000,28.2500,[v1,staff1])-note(n294,75,491370,495960,73,1,0). +snote(n296,[B,#],4,15:1,0,1/8,28.0000,28.5000,[v2,staff1])-note(n296,72,491500,495660,62,1,0). +snote(n297,[F,#],4,15:1,0,1/16,28.0000,28.2500,[v3,staff1])-note(n297,66,491550,493450,55,1,0). +snote(n298,[G,#],2,15:1,0,1/4,28.0000,29.0000,[v7,staff2])-note(n295,44,491480,506040,55,1,0). +snote(n299,[E,n],5,15:1,1/16,1/16,28.2500,28.5000,[v1,staff1])-note(n298,76,495490,497460,74,1,0). +snote(n300,[G,#],4,15:1,1/16,1/16,28.2500,28.5000,[v3,staff1])-note(n300,68,495700,497400,52,1,0). +snote(n301,[G,#],3,15:1,1/16,1/8,28.2500,28.7500,[v4,staff2,accent])-note(n299,56,495600,498690,44,1,0). +snote(n302,[E,n],5,15:1,1/8,1/16,28.5000,28.7500,[v1,staff1])-note(n301,76,498750,501510,71,1,0). +snote(n303,[A,#],4,15:1,1/8,1/16,28.5000,28.7500,[v2,staff1])-note(n302,70,498860,499810,60,1,0). +snote(n304,[F,#],4,15:1,1/8,1/16,28.5000,28.7500,[v3,staff1])-note(n303,66,498980,500290,52,1,0). +snote(n305,[D,#],5,15:1,3/16,1/16,28.7500,29.0000,[v1,staff1])-note(n304,75,501960,502920,74,1,0). +snote(n306,[B,#],4,15:1,3/16,1/16,28.7500,29.0000,[v2,staff1])-note(n307,72,502120,502380,51,1,0). +snote(n307,[G,#],4,15:1,3/16,1/16,28.7500,29.0000,[v3,staff1])-note(n305,68,502080,502730,59,1,0). +snote(n308,[G,#],3,15:1,3/16,1/16,28.7500,29.0000,[v4,staff2])-note(n306,56,502110,504240,52,1,0). +snote(n309,[C,#],5,15:2,0,1/16,29.0000,29.2500,[v1,staff1])-note(n309,73,505410,509170,75,1,0). +snote(n310,[E,n],4,15:2,0,1/16,29.0000,29.2500,[v3,staff1])-note(n310,64,505500,509030,62,1,0). +snote(n311,[C,#],3,15:2,0,1/4,29.0000,30.0000,[v7,staff2])-note(n308,49,505390,516460,52,1,0). +snote(n312,[D,#],5,15:2,1/16,1/16,29.2500,29.5000,[v1,staff1])-note(n311,75,508820,511110,76,1,0). +snote(n313,[G,#],4,15:2,1/16,1/16,29.2500,29.5000,[v3,staff1])-note(n313,68,508990,511940,56,1,0). +snote(n314,[G,#],3,15:2,1/16,1/8,29.2500,29.7500,[v4,staff2,accent])-note(n312,56,508840,511820,49,1,0). +snote(n315,[C,n],5,15:2,1/8,1/16,29.5000,29.7500,[v1,staff1])-note(n314,72,512030,516370,72,1,0). +snote(n316,[E,n],4,15:2,1/8,1/16,29.5000,29.7500,[v3,staff1])-note(n315,64,512200,514620,60,1,0). +snote(n317,[C,#],5,15:2,3/16,1/16,29.7500,30.0000,[v1,staff1])-note(n316,73,515590,516720,75,1,0). +snote(n318,[G,#],4,15:2,3/16,1/16,29.7500,30.0000,[v3,staff1])-note(n318,68,515650,516190,61,1,0). +snote(n319,[G,#],3,15:2,3/16,1/16,29.7500,30.0000,[v4,staff2])-note(n317,56,515610,516670,57,1,0). +snote(n320,[E,n],5,16:1,0,1/16,30.0000,30.2500,[v1,staff1])-note(n319,76,519870,522340,80,1,0). +snote(n321,[E,n],4,16:1,0,1/16,30.0000,30.2500,[v2,staff1])-note(n322,64,520030,521750,61,1,0). +snote(n322,[A,#],4,16:1,0,1/16,30.0000,30.2500,[v2,staff1])-note(n321,70,520020,522790,58,1,0). +snote(n323,[A,#],3,16:1,0,1/16,30.0000,30.2500,[v4,staff2])-note(n320,58,519980,522610,45,1,0). +snote(n324,[F,#],3,16:1,0,1/16,30.0000,30.2500,[v4,staff2])-note(n325,54,520140,522870,39,1,0). +snote(n325,[E,n],3,16:1,0,1/16,30.0000,30.2500,[v4,staff2])-note(n324,52,520130,522730,40,1,0). +snote(n326,[C,#],3,16:1,0,1/16,30.0000,30.2500,[v7,staff2])-note(n323,49,520080,522730,43,1,0). +snote(n327,[F,#],5,16:1,1/16,1/16,30.2500,30.5000,[v1,staff1])-note(n326,78,524190,525480,78,1,0). +snote(n328,[A,#],4,16:1,1/16,1/16,30.2500,30.5000,[v2,staff1])-note(n332,70,524470,526220,57,1,0). +snote(n329,[F,#],4,16:1,1/16,1/16,30.2500,30.5000,[v2,staff1])-note(n328,66,524270,525680,69,1,0). +snote(n330,[A,#],3,16:1,1/16,1/16,30.2500,30.5000,[v4,staff2])-note(n327,58,524240,525730,58,1,0). +snote(n331,[F,#],3,16:1,1/16,1/16,30.2500,30.5000,[v4,staff2])-note(n329,54,524370,526030,45,1,0). +snote(n332,[E,n],3,16:1,1/16,1/16,30.2500,30.5000,[v4,staff2])-note(n330,52,524400,526060,42,1,0). +snote(n333,[C,#],3,16:1,1/16,1/16,30.2500,30.5000,[v7,staff2])-note(n331,49,524410,525630,44,1,0). +snote(n334,[D,#],5,16:1,1/8,1/16,30.5000,30.7500,[v1,staff1])-note(n334,75,527630,529870,83,1,0). +snote(n335,[A,#],4,16:1,1/8,1/16,30.5000,30.7500,[v2,staff1])-note(n339,70,527890,528240,50,1,0). +snote(n336,[D,#],4,16:1,1/8,1/16,30.5000,30.7500,[v2,staff1])-note(n335,63,527710,528890,80,1,0). +snote(n337,[A,#],3,16:1,1/8,1/16,30.5000,30.7500,[v4,staff2])-note(n333,58,527620,528970,68,1,0). +snote(n338,[F,#],3,16:1,1/8,1/16,30.5000,30.7500,[v4,staff2])-note(n337,54,527780,529190,51,1,0). +snote(n339,[E,n],3,16:1,1/8,1/16,30.5000,30.7500,[v4,staff2])-note(n338,52,527840,529460,44,1,0). +snote(n340,[C,#],3,16:1,1/8,1/16,30.5000,30.7500,[v7,staff2])-note(n336,49,527720,529600,58,1,0). +snote(n341,[E,n],5,16:1,3/16,1/16,30.7500,31.0000,[v1,staff1])-note(n341,76,530940,532040,87,1,0). +snote(n342,[A,#],4,16:1,3/16,1/16,30.7500,31.0000,[v2,staff1])-note(n344,70,530990,532200,61,1,0). +snote(n343,[E,n],4,16:1,3/16,1/16,30.7500,31.0000,[v2,staff1])-note(n343,64,530960,531570,77,1,0). +snote(n344,[A,#],3,16:1,3/16,1/16,30.7500,31.0000,[v4,staff1])-note(n340,58,530890,532250,67,1,0). +snote(n345,[F,#],3,16:1,3/16,1/16,30.7500,31.0000,[v4,staff2])-note(n345,54,531060,532350,53,1,0). +snote(n346,[E,n],3,16:1,3/16,1/16,30.7500,31.0000,[v4,staff2])-note(n346,52,531180,533570,42,1,0). +snote(n347,[C,#],3,16:1,3/16,1/16,30.7500,31.0000,[v7,staff2])-note(n342,49,530960,532300,55,1,0). +snote(n348,[F,#],5,16:2,0,1/16,31.0000,31.2500,[v1,staff1])-note(n347,78,534540,536600,85,1,0). +snote(n349,[F,#],4,16:2,0,1/16,31.0000,31.2500,[v2,staff1])-note(n350,66,534600,536410,75,1,0). +snote(n350,[A,#],4,16:2,0,1/16,31.0000,31.2500,[v2,staff1])-note(n351,70,534620,540680,73,1,0). +snote(n351,[A,#],3,16:2,0,1/16,31.0000,31.2500,[v4,staff2])-note(n348,58,534550,536830,67,1,0). +snote(n352,[F,#],3,16:2,0,1/16,31.0000,31.2500,[v4,staff2])-note(n349,54,534600,539640,57,1,0). +snote(n353,[E,n],3,16:2,0,1/16,31.0000,31.2500,[v4,staff2])-note(n353,52,534720,536980,57,1,0). +snote(n354,[C,n],3,16:2,0,1/16,31.0000,31.2500,[v7,staff2])-note(n352,48,534670,536880,61,1,0). +snote(n355,[G,#],5,16:2,1/16,1/16,31.2500,31.5000,[v1,staff1])-note(n354,80,538260,539290,87,1,0). +snote(n356,[A,#],4,16:2,1/16,1/16,31.2500,31.5000,[v2,staff1])-deletion. +snote(n357,[G,#],4,16:2,1/16,1/16,31.2500,31.5000,[v2,staff1])-note(n358,68,538380,539390,73,1,0). +snote(n358,[A,#],3,16:2,1/16,1/16,31.2500,31.5000,[v4,staff2])-note(n355,58,538270,539300,68,1,0). +snote(n359,[F,#],3,16:2,1/16,1/16,31.2500,31.5000,[v4,staff2])-deletion. +snote(n360,[E,n],3,16:2,1/16,1/16,31.2500,31.5000,[v4,staff2])-note(n357,52,538360,539860,56,1,0). +snote(n361,[C,n],3,16:2,1/16,1/16,31.2500,31.5000,[v7,staff2])-note(n356,48,538350,539110,58,1,0). +snote(n362,[E,n],5,16:2,1/8,1/16,31.5000,31.7500,[v1,staff1])-note(n359,76,541670,543030,87,1,0). +snote(n363,[A,#],4,16:2,1/8,1/16,31.5000,31.7500,[v2,staff1])-note(n364,70,541810,543300,66,1,0). +snote(n364,[E,n],4,16:2,1/8,1/16,31.5000,31.7500,[v2,staff1])-note(n361,64,541720,542640,82,1,0). +snote(n365,[A,#],3,16:2,1/8,1/16,31.5000,31.7500,[v4,staff2])-note(n360,58,541690,542980,77,1,0). +snote(n367,[F,#],3,16:2,1/8,1/16,31.5000,31.7500,[v4,staff2])-note(n363,54,541800,543350,65,1,0). +snote(n368,[E,n],3,16:2,1/8,1/16,31.5000,31.7500,[v4,staff2])-note(n365,52,541890,543490,57,1,0). +snote(n366,[C,n],3,16:2,1/8,1/16,31.5000,31.7500,[v7,staff2])-note(n362,48,541800,543030,65,1,0). +snote(n369,[F,#],5,16:2,3/16,1/16,31.7500,32.0000,[v1,staff1])-note(n367,78,545860,547830,87,1,0). +snote(n370,[A,#],4,16:2,3/16,1/16,31.7500,32.0000,[v2,staff1])-note(n369,70,545900,548120,78,1,0). +snote(n371,[F,#],4,16:2,3/16,1/16,31.7500,32.0000,[v2,staff1])-note(n368,66,545890,547630,82,1,0). +snote(n372,[A,#],3,16:2,3/16,1/16,31.7500,32.0000,[v4,staff2])-note(n366,58,545860,547790,75,1,0). +snote(n373,[F,#],3,16:2,3/16,1/16,31.7500,32.0000,[v4,staff2])-note(n370,54,545920,548050,66,1,0). +snote(n374,[E,n],3,16:2,3/16,1/16,31.7500,32.0000,[v4,staff2])-note(n372,52,546130,548300,50,1,0). +snote(n375,[C,n],3,16:2,3/16,1/16,31.7500,32.0000,[v7,staff2])-note(n371,48,545950,547700,66,1,0). +snote(n376,[G,#],5,17:1,0,5/16,32.0000,33.2500,[v1,staff1,accent])-note(n373,80,552190,570300,87,1,0). +snote(n377,[E,n],5,17:1,0,1/4,32.0000,33.0000,[v2,staff1])-note(n378,76,552330,571070,69,1,0). +snote(n378,[G,#],4,17:1,0,1/16,32.0000,32.2500,[v3,staff1])-note(n374,68,552280,558460,71,1,0). +snote(n379,[B,n],3,17:1,0,1/2,32.0000,34.0000,[v4,staff2])-note(n375,59,552300,583580,63,1,0). +snote(n380,[G,#],3,17:1,0,1/2,32.0000,34.0000,[v4,staff2])-note(n377,56,552330,583800,56,1,0). +snote(n381,[E,n],3,17:1,0,1/2,32.0000,34.0000,[v4,staff2])-note(n379,52,552420,584470,50,1,0). +snote(n382,[B,n],2,17:1,0,1/2,32.0000,34.0000,[v7,staff2])-note(n376,47,552330,584020,59,1,0). +snote(n383,[B,n],4,17:1,1/16,1/16,32.2500,32.5000,[v3,staff1])-note(n380,71,558320,563200,62,1,0). +snote(n384,[G,#],4,17:1,1/8,1/16,32.5000,32.7500,[v3,staff1])-note(n381,68,562690,566540,56,1,0). +snote(n385,[B,n],4,17:1,3/16,1/16,32.7500,33.0000,[v3,staff1])-note(n382,71,566410,570570,60,1,0). +snote(n386,[G,#],4,17:2,0,1/16,33.0000,33.2500,[v3,staff1])-note(n383,68,569890,571670,56,1,0). +snote(n387,[F,#],5,17:2,1/16,1/16,33.2500,33.5000,[v1,staff1])-note(n384,78,573630,575600,67,1,0). +snote(n388,[B,n],4,17:2,1/16,1/16,33.2500,33.5000,[v3,staff1])-note(n385,71,573880,576850,48,1,0). +snote(n389,[E,n],5,17:2,1/8,1/16,33.5000,33.7500,[v1,staff1])-note(n386,76,577600,581790,66,1,0). +snote(n390,[G,#],4,17:2,1/8,1/16,33.5000,33.7500,[v3,staff1])-note(n387,68,577930,580600,40,1,0). +snote(n391,[C,#],5,17:2,3/16,1/16,33.7500,34.0000,[v1,staff1])-note(n388,73,581900,584080,53,1,0). +snote(n392,[B,n],4,17:2,3/16,1/16,33.7500,34.0000,[v3,staff1])-note(n389,71,581970,583060,48,1,0). +snote(n393,[D,#],5,18:1,0,1/4,34.0000,35.0000,[v1,staff1])-note(n390,75,587360,600920,59,1,0). +snote(n394,[D,#],4,18:1,0,1/16,34.0000,34.2500,[v3,staff1])-note(n391,63,587670,592590,45,1,0). +snote(n395,[F,#],3,18:1,0,1/16,34.0000,34.2500,[v4,staff2])-note(n392,54,587790,601180,25,1,0). +snote(n396,[F,#],4,18:1,1/16,1/16,34.2500,34.5000,[v3,staff1])-note(n393,66,592710,597610,39,1,0). +snote(n397,[B,n],3,18:1,1/16,1/16,34.2500,34.5000,[v4,staff2])-note(n394,59,592880,596330,28,1,0). +snote(n398,[D,#],4,18:1,1/8,1/16,34.5000,34.7500,[v3,staff1])-note(n395,63,596710,600240,36,1,0). +snote(n399,[B,n],2,18:1,1/8,1/16,34.5000,34.7500,[v4,staff2])-note(n396,47,596750,600210,13,1,0). +snote(n400,[F,#],4,18:1,3/16,1/16,34.7500,35.0000,[v3,staff1])-note(n397,66,600010,601710,46,1,0). +snote(n401,[B,n],3,18:1,3/16,1/16,34.7500,35.0000,[v4,staff2])-note(n398,59,600310,603310,38,1,0). +snote(n402,[E,n],5,18:2,0,1/16,35.0000,35.2500,[v1,staff1])-note(n399,76,603670,608440,62,1,0). +snote(n403,[E,n],4,18:2,0,1/16,35.0000,35.2500,[v3,staff1])-note(n400,64,603870,607870,46,1,0). +snote(n404,[C,#],3,18:2,0,1/16,35.0000,35.2500,[v4,staff2])-note(n401,49,603890,608550,35,1,0). +snote(n405,[D,#],5,18:2,1/16,1/16,35.2500,35.5000,[v1,staff1])-note(n402,75,607250,611630,66,1,0). +snote(n406,[G,#],4,18:2,1/16,1/16,35.2500,35.5000,[v3,staff1])-note(n403,68,607380,611250,50,1,0). +snote(n407,[G,#],3,18:2,1/16,1/16,35.2500,35.5000,[v4,staff2])-note(n404,56,607610,611150,32,1,0). +snote(n408,[C,#],5,18:2,1/8,1/16,35.5000,35.7500,[v1,staff1])-note(n405,73,610840,615680,62,1,0). +snote(n409,[E,n],4,18:2,1/8,1/16,35.5000,35.7500,[v3,staff1])-note(n406,64,610960,613670,44,1,0). +snote(n410,[G,#],2,18:2,1/8,1/16,35.5000,35.7500,[v4,staff2])-note(n407,44,611000,617540,35,1,0). +snote(n411,[G,#],4,18:2,3/16,1/16,35.7500,36.0000,[v1,staff1])-note(n408,68,614910,616620,52,1,0). +snote(n412,[G,#],3,18:2,3/16,1/16,35.7500,36.0000,[v4,staff2])-note(n409,56,614920,617420,33,1,0). +snote(n413,[B,n],4,19:1,0,1/4,36.0000,37.0000,[v1,staff1])-note(n410,71,619990,631930,54,1,0). +snote(n414,[B,n],3,19:1,0,1/16,36.0000,36.2500,[v3,staff1])-note(n412,59,620360,624530,36,1,0). +snote(n415,[D,#],3,19:1,0,1/16,36.0000,36.2500,[v4,staff2])-note(n411,51,620000,633190,31,1,0). +snote(n416,[D,#],4,19:1,1/16,1/16,36.2500,36.5000,[v3,staff1])-note(n413,63,624210,628420,40,1,0). +snote(n417,[G,#],3,19:1,1/16,1/16,36.2500,36.5000,[v4,staff2])-note(n414,56,624340,627580,29,1,0). +snote(n418,[B,n],3,19:1,1/8,1/16,36.5000,36.7500,[v3,staff1])-note(n416,59,627920,631410,36,1,0). +snote(n419,[G,#],2,19:1,1/8,1/16,36.5000,36.7500,[v4,staff2])-note(n415,44,627800,631310,30,1,0). +snote(n420,[D,#],4,19:1,3/16,1/16,36.7500,37.0000,[v3,staff1])-note(n417,63,631760,632320,43,1,0). +snote(n421,[G,#],3,19:1,3/16,1/16,36.7500,37.0000,[v4,staff2])-note(n418,56,631870,632380,7,1,0). +snote(n422,[C,#],5,19:2,0,1/16,37.0000,37.2500,[v1,staff1])-note(n419,73,636150,638250,54,1,0). +snote(n423,[C,#],4,19:2,0,1/16,37.0000,37.2500,[v3,staff1])-note(n421,61,636510,640610,35,1,0). +snote(n424,[A,n],2,19:2,0,1/16,37.0000,37.2500,[v4,staff2])-note(n420,45,636430,640560,30,1,0). +snote(n425,[B,n],4,19:2,1/16,1/16,37.2500,37.5000,[v1,staff1])-note(n423,71,640500,644420,48,1,0). +snote(n426,[E,n],4,19:2,1/16,1/16,37.2500,37.5000,[v3,staff1])-note(n424,64,640810,643960,31,1,0). +snote(n427,[E,n],3,19:2,1/16,1/16,37.2500,37.5000,[v4,staff2])-note(n422,52,640470,644430,37,1,0). +snote(n428,[A,n],4,19:2,1/8,1/16,37.5000,37.7500,[v1,staff1])-note(n425,69,644100,648980,48,1,0). +snote(n429,[C,#],4,19:2,1/8,1/16,37.5000,37.7500,[v3,staff1])-note(n427,61,644370,647270,23,1,0). +snote(n430,[E,n],2,19:2,1/8,1/16,37.5000,37.7500,[v4,staff2])-note(n426,40,644250,649890,32,1,0). +snote(n431,[E,n],4,19:2,3/16,1/16,37.7500,38.0000,[v1,staff1])-note(n428,64,647900,649420,49,1,0). +snote(n432,[E,n],3,19:2,3/16,1/16,37.7500,38.0000,[v4,staff2])-note(n429,52,647930,650500,32,1,0). +snote(n433,[G,#],4,20:1,0,1/2,38.0000,40.0000,[v1,staff1])-note(n430,68,652680,685200,46,1,0). +snote(n434,[G,#],3,20:1,0,1/16,38.0000,38.2500,[v3,staff1])-note(n432,56,653160,657670,26,1,0). +snote(n435,[B,n],2,20:1,0,1/16,38.0000,38.2500,[v4,staff2])-note(n431,47,652910,663200,23,1,0). +snote(n436,[B,n],3,20:1,1/16,1/16,38.2500,38.5000,[v3,staff1])-note(n434,59,657880,662140,30,1,0). +snote(n437,[E,n],3,20:1,1/16,1/16,38.2500,38.5000,[v4,staff2])-note(n433,52,657710,661710,19,1,0). +snote(n438,[G,#],3,20:1,1/8,1/16,38.5000,38.7500,[v3,staff1])-note(n436,56,661960,665660,15,1,0). +snote(n439,[E,n],2,20:1,1/8,1/16,38.5000,38.7500,[v4,staff2])-note(n435,40,661710,670300,23,1,0). +snote(n440,[B,n],3,20:1,3/16,1/16,38.7500,39.0000,[v3,staff1])-note(n438,59,665720,670130,34,1,0). +snote(n441,[E,n],3,20:1,3/16,1/16,38.7500,39.0000,[v4,staff2])-note(n437,52,665520,670230,31,1,0). +snote(n442,[G,#],3,20:2,0,1/16,39.0000,39.2500,[v3,staff1])-note(n440,56,669760,673640,25,1,0). +snote(n443,[B,n],2,20:2,0,1/16,39.0000,39.2500,[v4,staff2])-note(n439,47,669610,679190,28,1,0). +snote(n444,[B,n],3,20:2,1/16,1/16,39.2500,39.5000,[v3,staff1])-note(n441,59,673620,678870,27,1,0). +snote(n445,[E,n],3,20:2,1/16,1/16,39.2500,39.5000,[v4,staff2])-note(n442,52,673980,678130,19,1,0). +snote(n446,[G,#],3,20:2,1/8,1/16,39.5000,39.7500,[v3,staff1])-note(n443,56,678140,683800,23,1,0). +snote(n447,[E,n],2,20:2,1/8,3/8,39.5000,41.0000,[v5,staff2])-note(n444,40,678170,704670,22,1,0). +snote(n448,[B,n],3,20:2,3/16,1/16,39.7500,40.0000,[v3,staff1])-note(n445,59,683550,685070,30,1,0). +snote(n449,[B,n],2,20:2,3/16,5/16,39.7500,41.0000,[v4,staff2])-note(n446,47,683590,705800,18,1,0). +snote(n450,[G,#],4,21:1,0,0,40.0000,40.0000,[v1,staff1])-note(n447,68,691330,694180,38,1,0). +snote(n451,[F,#],4,21:1,0,0,40.0000,40.0000,[v1,staff1])-note(n450,66,693140,695700,44,1,0). +snote(n452,[E,n],4,21:1,0,1/4,40.0000,41.0000,[v1,staff1])-note(n451,64,695050,705530,40,1,0). +snote(n453,[G,#],3,21:1,0,1/4,40.0000,41.0000,[v3,staff1])-note(n449,56,691800,703570,28,1,0). +snote(n454,[E,n],3,21:1,0,1/4,40.0000,41.0000,[v4,staff2])-deletion. sustain(0,11). sustain(400,11). sustain(480,11). diff --git a/tests/test_performance_expressions.py b/tests/test_performance_features.py similarity index 100% rename from tests/test_performance_expressions.py rename to tests/test_performance_features.py From cc7476adf9b3f1abb9a2f7779b3e694c251119f8 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 11:52:45 +0200 Subject: [PATCH 107/122] remove map_fields function --- .../musicanalysis/performance_features.py | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 178cbced..d90d8042 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -211,26 +211,6 @@ def print_performance_feats_functions(): list_performance_feature_functions = list_performance_feats_functions print_performance_feature_functions = print_performance_feats_functions - -def map_fields(note_info, fields): - """ - map the one-hot fields of dynamics and articulation marking into one column with field. - - Args: - note_info (np.array): a row slice from the note_array, without dtype names - fields (list): list of tuples (index, field name) - - Returns: - string: the name of the marking. - """ - - for i, name in fields: - if note_info[i] == 1: - # hack for the return type - return np.array([name.split(".")[-1]], dtype="U256") - return np.array(["N/A"], dtype="U256") - - ### Asynchrony def asynchrony_feature(m_score: np.ndarray, From 32ffd0cc49dbe1b1b57b5f05032b630b26047111 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 12:17:29 +0200 Subject: [PATCH 108/122] fix test ordering --- tests/test_note_features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_note_features.py b/tests/test_note_features.py index 93004fa3..891a254b 100644 --- a/tests/test_note_features.py +++ b/tests/test_note_features.py @@ -49,7 +49,7 @@ def test_slur_grace_art_dyn_orn(self): trilltest = na["ornament_feature.trill-mark"] == np.array( [0, 0, 1, 0, 0, 0] ) - gracetest = na["grace_feature.grace_note"] == np.array([0, 0, 0, 0, 1, 1]) + gracetest = na["grace_feature.grace_note"] == np.array([0, 0, 0, 1, 0, 1]) dyntest = na["loudness_direction_feature.f"] == np.array([0, 0, 0, 1, 1, 1]) slurtest = na["slur_feature.slur_decr"] == np.array([0, 0, 0, 1, 1, 1]) self.assertTrue(np.all(stactest), "staccato feature does not match") From e06332f153f818e29f4ece36411a4820ffed9e81 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 12:30:28 +0200 Subject: [PATCH 109/122] change version in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index abcdaa8a..842b7792 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ URL = "https://github.com/CPJKU/partitura" EMAIL = "partitura-users@googlegroups.com" AUTHOR = "Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier" -REQUIRES_PYTHON = ">=3.6" +REQUIRES_PYTHON = ">=3.7" VERSION = "1.2.2" # What packages are required for this module to be executed? From cea45d7db613829c47d181a5eba197f5c41bd4c9 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 9 Jun 2023 12:43:58 +0200 Subject: [PATCH 110/122] resolves #264 --- partitura/musicanalysis/note_features.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index 917d51f4..74beee28 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -587,9 +587,8 @@ def tempo_direction_feature(na, part, **kwargs): """ onsets = na["onset_div"] N = len(onsets) - constant = ["adagio", "largo", "lento", "grave", "largo", "lento", "grave", "lento", - "larghetto", "adagietto", "andante", "andantino", "moderato", "allegretto", - "allegro", "vivace", "presto", "prestissimo", "tempo_incr", "tempo_decr", + constant = ["adagio", "largo", "lento", "grave", "larghetto", "adagietto", "andante", + "andantino", "moderato", "allegretto", "allegro", "vivace", "presto", "prestissimo", "unknown_constant"] names = constant + ["tempo_incr", "tempo_decr"] directions = list(part.iter_all(score.TempoDirection, include_subclasses=True)) From 0ed09aa9cb0964738bdeafd52eab7afbeb996344 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 14:53:54 +0200 Subject: [PATCH 111/122] remove voice fields from test --- tests/test_performance_features.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/test_performance_features.py b/tests/test_performance_features.py index b67ff52c..cf2187b9 100644 --- a/tests/test_performance_features.py +++ b/tests/test_performance_features.py @@ -14,16 +14,13 @@ class TestPerformanceFeatures(unittest.TestCase): def test_performance_features(self): - - True_array = np.array([('n1', 0.23374297, 0. , 0. , 0. , 0. , 89.74999 , 62.000057, 0., 0.16015087, -0.5, 0.5 , 59, 4.9925 , 0.8775 , 44, 1, 0., 0., 1.4700003), - ('n4', 0.03011051, 0.07375002, -0.20350015, 0.3837298, 0.03447518, 114.25004 , 61.000244, 0., 0.4027142 , 0. , 1. , 40, 5.7025 , 2.4375 , 22, 7, 0., 0., 2.8474998), - ('n3', 2.527984 , 0.07375002, -0.20350015, 0.3837298, 0.03447518, 87.500046, 61.000244, 0., 0.4027142 , 0. , 0.25, 56, 5.77625, 2.36375, 26, 3, 0., 0., 2.8474998)], + fields = ['id','pedal_feature.onset_value','pedal_feature.offset_value','pedal_feature.to_prev_release', + 'pedal_feature.to_next_release','onset','duration', 'pitch', 'p_onset', 'p_duration','velocity', 'beat_period'] + True_array = np.array([('n1', 0.23374297, 89.74999 , 62.000057, 0., 0.16015087, -0.5, 0.5 , 59, 4.9925 , 0.8775 , 44, 1.4700003), + ('n4', 0.03011051, 114.25004 , 61.000244, 0., 0.4027142 , 0. , 1. , 40, 5.7025 , 2.4375 , 22, 2.8474998), + ('n3', 2.527984 , 87.500046, 61.000244, 0., 0.4027142 , 0. , 0.25, 56, 5.77625, 2.36375, 26, 2.8474998)], dtype=[('id', ' Date: Fri, 9 Jun 2023 15:42:28 +0200 Subject: [PATCH 112/122] convert to lexsort for consistency --- partitura/musicanalysis/note_array_to_score.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index 9e4fee3f..c8b9dd9e 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -324,11 +324,11 @@ def note_array_to_score( if all([x not in dtypes for x in case1_ex]): onset_time = "onset_beat" - pitch_sort_idx = np.argsort(note_array["pitch"]) - note_array = note_array[pitch_sort_idx] - onset_sort_idx = np.argsort(note_array[onset_time], kind="mergesort") - note_array = note_array[onset_sort_idx] - + + # Order Lexicographically + sort_idx = np.lexsort((note_array["duration_div"], note_array["pitch"], note_array["onset_div"])) + note_array = note_array[sort_idx] + # case 1, estimate divs if all([x in dtypes for x in case1] and [x not in dtypes for x in case1_ex]): @@ -428,8 +428,7 @@ def note_array_to_score( note_ids = ["{}n{:4d}".format(name_id, i) for i in range(len(note_array))] note_array["id"] = np.array(note_ids) - # Order Lexicographically - note_array = note_array[np.lexsort((note_array["onset_div"], note_array["pitch"]))] + # estimate voice if "voice" in dtypes: From 61899aadf19ca0f470386f167f30eee5eb3d9cd1 Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Fri, 9 Jun 2023 15:50:47 +0200 Subject: [PATCH 113/122] Correcting minor typo errot --- partitura/musicanalysis/note_array_to_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index c8b9dd9e..bae042e0 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -326,7 +326,7 @@ def note_array_to_score( # Order Lexicographically - sort_idx = np.lexsort((note_array["duration_div"], note_array["pitch"], note_array["onset_div"])) + sort_idx = np.lexsort((note_array[onset_time], note_array["pitch"])) note_array = note_array[sort_idx] From de72258d3eec230085a6611f364b17f4b41f615d Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 15:51:37 +0200 Subject: [PATCH 114/122] flexible lexsort arguments --- partitura/musicanalysis/note_array_to_score.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py index c8b9dd9e..a1df4851 100644 --- a/partitura/musicanalysis/note_array_to_score.py +++ b/partitura/musicanalysis/note_array_to_score.py @@ -321,15 +321,15 @@ def note_array_to_score( # sort the array onset_time = "onset_div" + duration_time = "duration_div" if all([x not in dtypes for x in case1_ex]): onset_time = "onset_beat" - + duration_time = "duration_beat" # Order Lexicographically - sort_idx = np.lexsort((note_array["duration_div"], note_array["pitch"], note_array["onset_div"])) + sort_idx = np.lexsort((note_array[duration_time], note_array["pitch"], note_array[onset_time])) note_array = note_array[sort_idx] - # case 1, estimate divs if all([x in dtypes for x in case1] and [x not in dtypes for x in case1_ex]): # estimate onset_divs and duration_divs, assumes all beat times as quarters From 492b228bf1921530f9c2fc10de3fc5c0c66ce2ed Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 16:00:21 +0200 Subject: [PATCH 115/122] uncomment --- partitura/utils/generic.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/partitura/utils/generic.py b/partitura/utils/generic.py index 35c9e7ca..3490d84e 100644 --- a/partitura/utils/generic.py +++ b/partitura/utils/generic.py @@ -239,15 +239,15 @@ def replace_refs(self, o_map): if o_el in o_map: o_list_new.append(o_map[o_el]) else: - # warnings.warn( - # dedent( - # """reference not found in - # o_map: {} start={} end={}, substituting None - # """.format( - # o_el, o_el.start, o_el.end - # ) - # ) - # ) + warnings.warn( + dedent( + """reference not found in + o_map: {} start={} end={}, substituting None + """.format( + o_el, o_el.start, o_el.end + ) + ) + ) o_list_new.append(None) setattr(self, attr, o_list_new) From a4e1e1d3a9f6bfbccf661a8a169e560ff207a48a Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 16:01:18 +0200 Subject: [PATCH 116/122] remove numpy printoptions --- partitura/musicanalysis/performance_features.py | 1 - 1 file changed, 1 deletion(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index d90d8042..e973a57f 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -10,7 +10,6 @@ from typing import Union, List import warnings import numpy as np -np.set_printoptions(suppress=True) import matplotlib.pyplot as plt from scipy import stats from scipy.optimize import least_squares From a03d0642f292b1bcf9d471bad5bd00762de81835 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 9 Jun 2023 16:13:50 +0200 Subject: [PATCH 117/122] improve some docstrings --- .../musicanalysis/performance_features.py | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index e973a57f..91e2dede 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -350,15 +350,29 @@ def articulation_feature(m_score : np.ndarray, def get_kor(e1, e2): - """calculate the ratio between key overlap time and IOI. + """ + calculate the ratio between key overlap time and IOI. In the case of a negative IOI (the order of notes in performance is reversed from the score), - set at default 0.""" + set at default 0. + is bounded within the interval [-1,5] + + Parameters + ---------- + e1 : np.ndarray + the m_score row of first note + e2 : np.ndarray + the m_score of second note + + Returns + ------- + kor : float + Key overlap ratio + """ kot = e1['p_offset'] - e2['p_onset'] ioi = e2['p_onset'] - e1['p_onset'] if ioi <= 0: - # warnings.warn(f"Getting KOR smaller than -1 in {e1['onset']}-{e1['pitch']} and {e2['onset']}-{e2['pitch']}.") kor = 0 kor = kot / ioi @@ -369,8 +383,18 @@ def get_kor(e1, e2): def get_next_note(note_info, match_voiced): """ get the next note in the same voice that's a reasonable transition - note_info: the row of current note - match_voiced: all notes in the same voice + + Parameters + ---------- + note_info : np.ndarray + the row of current note + match_voiced : np.ndarray + all notes in the same voice + + Returns + ------- + next_position : np.ndarray + the next note """ next_position = min(o for o in match_voiced['onset'] if o > note_info['onset']) From b341c7f9a7110ee951e522fce2db14c9509c5ac7 Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Fri, 9 Jun 2023 16:30:21 +0200 Subject: [PATCH 118/122] Update introduction.rst --- docs/source/introduction.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 0f6f6af6..e9f4644b 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -147,6 +147,14 @@ alignments, that are not handled by music21. .. `partitura` intends to provide a convenient way to work with symbolic .. musical data in the context of problems such as musical expression modeling, or music generation. Although it is not the main aim of the package to provide music analysis tools, the package does offer functionality for pitch spelling, voice assignment and key estimation. +Additional Resources +==================== +For a more hands on tutorial of `partitura` for providing an introduction to symbolic music processing for a broad MIR audience, with a particular focus on showing how to extract relevant MIR features from symbolic musical formats in a fast, intuitive, and scalable way. + +Please visit: +`https://cpjku.github.io/partitura_tutorial/ `_ + + Credits ======= @@ -165,6 +173,8 @@ If you find Partitura useful, we would appreciate if you could cite us! } + + Acknowledgments --------------- From 01716430904efb7f23840f2053375d53c4427700 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 9 Jun 2023 17:01:08 +0200 Subject: [PATCH 119/122] updated changes.md --- CHANGES.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index bc227793..a361ffe4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,49 @@ Release Notes ============= + +New Version 1.3.0 (Released on 2023-06-09) +------------------------------------------ + +This PR addresses release 1.3.0, it includes several bug fixes, code cleaning, documentation, and new functionality. + + +New Features +------------ + +- Enhanced Performance features in the same fashion as the note features; +- Fixed-size option for Note features. Use: ` +- Create a score from a note array functionality. Call `partitura.musicanalysis.scorify(note_array)`; + +New Optional Features +--------------------- + +- _If music21 is installed_ : Import music21 to Partitura by calling `partitura.load_music21(m21_score)` +- _If MidiTok is installed_ : Export Partitura Score to Tokens by calling `partitura.utils.music.tokenize(score_data, tokenizer)` + +Bug Fixes +---------- + +- Fixed bug: #264 +- Fixed bug: #251 +- Fixed bug: #207 +- Fixed bug: #162 +- Fixed bug: #261 +- Fixed bug: #262 +- Fixed Issue: #256 +- Addressed Issue: #133 +- Fixed bug: #257 +- Fixed bug: #248 +- Fixed bug: #223 + +Other Changes +------------- + +- Minor Changes to the Documentation +- Addition of Docs link to the Github header +- Upgraded python version requirements to Python>= 3.7 + + Version 1.2.2 (Released on 2023-10-05) -------------------------------------- From 7f7d8b776b9054c39a0e659f9e0f4955e5c56711 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 9 Jun 2023 17:03:00 +0200 Subject: [PATCH 120/122] updated release version. --- docs/source/conf.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 4876209e..ca48042d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,9 +29,9 @@ # built documents. # # The short X.Y version. -version = "1.2.2" # pkg_resources.get_distribution("partitura").version +version = "1.3.0" # pkg_resources.get_distribution("partitura").version # The full version, including alpha/beta/rc tags. -release = "1.2.2" +release = "1.3.0" # # The full version, including alpha/beta/rc tags # release = pkg_resources.get_distribution("partitura").version diff --git a/setup.py b/setup.py index 842b7792..9d032b88 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ EMAIL = "partitura-users@googlegroups.com" AUTHOR = "Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier" REQUIRES_PYTHON = ">=3.7" -VERSION = "1.2.2" +VERSION = "1.3.0" # What packages are required for this module to be executed? REQUIRED = ["numpy", "scipy", "lxml", "lark-parser", "xmlschema", "mido"] From d56ef157c42a5d2198f8ae19454bcad44623f8eb Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 9 Jun 2023 17:22:39 +0200 Subject: [PATCH 121/122] renamed compute_performance_features to make_performance_features to make it uniform with make_note_features. --- partitura/musicanalysis/__init__.py | 4 ++-- .../musicanalysis/performance_features.py | 18 +++++++++--------- tests/test_performance_features.py | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/partitura/musicanalysis/__init__.py b/partitura/musicanalysis/__init__.py index 63f7d4d3..5c36ab90 100644 --- a/partitura/musicanalysis/__init__.py +++ b/partitura/musicanalysis/__init__.py @@ -21,7 +21,7 @@ make_rest_features, ) from .performance_codec import encode_performance, decode_performance -from .performance_features import compute_performance_features +from .performance_features import make_performance_features from .note_array_to_score import note_array_to_score @@ -38,6 +38,6 @@ "decode_performance", "compute_note_array", "full_note_array", - "compute_performance_features", + "make_performance_features", "note_array_to_score" ] diff --git a/partitura/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py index 91e2dede..5a4e7e98 100644 --- a/partitura/musicanalysis/performance_features.py +++ b/partitura/musicanalysis/performance_features.py @@ -22,7 +22,7 @@ __all__ = [ - "compute_performance_features", + "make_performance_features", ] # ordinal @@ -32,12 +32,12 @@ class InvalidPerformanceFeatureException(Exception): pass -def compute_performance_features(score: ScoreLike, - performance: PerformanceLike, - alignment: list, - feature_functions: Union[List, str], - add_idx: bool = True -): +def make_performance_features(score: ScoreLike, + performance: PerformanceLike, + alignment: list, + feature_functions: Union[List, str], + add_idx: bool = True + ): """ Compute the performance features. This function is defined in the same style of note_features.make_note_features @@ -164,9 +164,9 @@ def list_performance_feats_functions(): """Return a list of all feature function names defined in this module. The feature function names listed here can be specified by name in - the `compute_performance_features` function. For example: + the `make_performance_features` function. For example: - >>> feature, names = compute_performance_features(score, + >>> feature, names = make_performance_features(score, performance, alignment, ['asynchrony_feature']) diff --git a/tests/test_performance_features.py b/tests/test_performance_features.py index cf2187b9..e0c4723a 100644 --- a/tests/test_performance_features.py +++ b/tests/test_performance_features.py @@ -7,7 +7,7 @@ import numpy as np from partitura import load_match from tests import MATCH_EXPRESSIVE_FEATURES_TESTFILES -from partitura.musicanalysis.performance_features import compute_performance_features +from partitura.musicanalysis.performance_features import make_performance_features import os @@ -34,9 +34,9 @@ def test_performance_features(self): ('beat_period', ' Date: Fri, 9 Jun 2023 20:02:19 +0200 Subject: [PATCH 122/122] update documentation string. --- partitura/musicanalysis/note_features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/partitura/musicanalysis/note_features.py b/partitura/musicanalysis/note_features.py index fc652226..28e5d4fa 100644 --- a/partitura/musicanalysis/note_features.py +++ b/partitura/musicanalysis/note_features.py @@ -99,8 +99,8 @@ def make_note_features( Parameters ---------- - score : Part - The score as a Part instance + part : ScoreLike + A partitura scoreLike object, can be Score, Part, or PartGroup. feature_functions : list or str A list of feature functions. Elements of the list can be either the functions themselves or the names of a feature function as