diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index 1b75dcce..8f3efe44 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -25,6 +25,10 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . + - name: Install Optional dependencies + run: | + 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: | pip install coverage 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/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) -------------------------------------- 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/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 --------------- diff --git a/partitura/__init__.py b/partitura/__init__.py index 074b8726..c624b578 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 @@ -26,6 +27,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/__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/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/io/exportmidi.py b/partitura/io/exportmidi.py index f256ec80..95b95359 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,63 @@ 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 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/io/importmatch.py b/partitura/io/importmatch.py index c384a40d..ee1bf443 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -584,14 +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 + is_tied = True # dictionary with keyword args with which the Note # (or GraceNote) will be instantiated @@ -677,6 +677,23 @@ 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 + # 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: ts_beats = tsg.numerator diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 09e2ff54..dcc8285d 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 ---------- @@ -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/importmidi.py b/partitura/io/importmidi.py index 4ff966d2..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 diff --git a/partitura/io/importmusic21.py b/partitura/io/importmusic21.py new file mode 100644 index 00000000..0e977a12 --- /dev/null +++ b/partitura/io/importmusic21.py @@ -0,0 +1,208 @@ +import partitura as pt +import numpy as np +from fractions import Fraction + +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: M21Score) -> pt.score.Score: + """ + Loads a music21 score object and returns a Partitura Score object. + + Parameters + ---------- + m21_score : :class:`music21.stream.Score` + The music21 score object, produced for example with the `music21.converter.parse()` function. + + Returns + ------- + 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(m21_score) + # create parts from the specifications in the music21 object + parser.create_parts() + # 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" + ) + scr = pt.score.Score( + id=doc_name, + partlist=parser.parts, + ) + return scr + + +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 + 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(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) + # 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): + pt_rest = pt.score.Rest( + id=m21_rest.id, + voice=self.find_voice(m21_rest), + staff=part_idx + 1, + symbolic_duration=m21_rest.duration.type, + 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) + 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 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="{}_{}".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, + 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), + id=generic_note.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 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 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""" + 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_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 diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index c4bbd70a..f93466f7 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 @@ -329,9 +328,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 @@ -456,13 +455,35 @@ def _parse_parts(document, part_dict): # shift.applied = True -def _handle_measure(measure_el, position, part, ongoing, doc_order): - """ - Parse a ... element, adding it and its contents to the - part. +def _handle_measure(measure_el, position, part, ongoing, doc_order, measure_counter): + """ 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 = make_measure(measure_el, measure_counter) # add the start of the measure to the time line part.add(measure, position) @@ -587,7 +608,7 @@ def _handle_measure(measure_el, position, part, ongoing, doc_order): # add end time of measure part.add(measure, None, measure_maxtime) - + return measure_maxtime, doc_order @@ -696,13 +717,26 @@ def _handle_new_system(position, part, ongoing): ongoing["system"] = system -def make_measure(xml_measure): +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 = get_value_from_attribute(xml_measure, "number", int) + + measure.number = measure_counter + measure.name = get_value_from_attribute(xml_measure, "number", str) + return measure 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/musicanalysis/__init__.py b/partitura/musicanalysis/__init__.py index 718b278a..5c36ab90 100644 --- a/partitura/musicanalysis/__init__.py +++ b/partitura/musicanalysis/__init__.py @@ -21,6 +21,8 @@ make_rest_features, ) from .performance_codec import encode_performance, decode_performance +from .performance_features import make_performance_features +from .note_array_to_score import note_array_to_score __all__ = [ @@ -36,4 +38,6 @@ "decode_performance", "compute_note_array", "full_note_array", + "make_performance_features", + "note_array_to_score" ] diff --git a/partitura/musicanalysis/note_array_to_score.py b/partitura/musicanalysis/note_array_to_score.py new file mode 100644 index 00000000..a1df4851 --- /dev/null +++ b/partitura/musicanalysis/note_array_to_score.py @@ -0,0 +1,522 @@ +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 +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: 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). + + 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 + The note array to which the divs fields will be added. + Normally only beat onset and duration are provided. + + Returns + ------- + 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"]] + 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)) + 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), 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", float), ("duration_beat", float)]) + return rfn.merge_arrays((note_array, na_beats), flatten=True, usemask=False) + + +def create_part( + ticks: int, + note_array: np.ndarray, + key_sigs: list = None, + time_sigs: list = None, + part_id: str = None, + part_name: str = None, + sanitize: bool = True, + anacrusis_divs: int = 0, + barebones: bool = False, +): + """ + 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 (optional) + A list of key signatures. Each key signature is a tuple of the form (onset, key_name, offset). + 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 (optional) + The name of the part + sanitize: bool (optional) + If True, then measures, tied-notes and triplets will be sanitized. + 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 + ------- + 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, ) + part.set_quarter_duration(0, ticks) + + clef = score.Clef( + staff=1, **estimate_clef_properties(note_array["pitch"]) + ) + part.add(clef, 0) + + # key sig + 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) + part.add(score.KeySignature(fifths, mode), t_start, t_end) + else: + warnings.warn("No key signatures added") + + # time sig + 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) + 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( + 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"]) + + warnings.warn("add measures", stacklevel=2) + + if not barebones and anacrusis_divs > 0: + part.add(score.Measure(0), 0, anacrusis_divs) + + if not barebones and sanitize: + warnings.warn("Inferring measures", stacklevel=2) + score.add_measures(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) + + # clean up + score.sanitize_part(part) + + warnings.warn("done create_part", stacklevel=2) + return part + + +def note_array_to_score( + note_array: Union[np.ndarray, list], + name_id: str = "", + divs: int = None, + key_sigs: list = None, + time_sigs: list = None, + part_name: str = "", + assign_note_ids: bool = True, + 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). + Note array should contain the following fields: + - onset_div or onset_beat + - duration_div or duration_beat + - pitch + + 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 + ---------- + 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 (optional) + - ts_beat_type (optional) + - key_mode(optional) + - key_fifths(optional) + - id (optional) + divs : int (optional) + 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 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) + Return a Partitura part object instead of a score. + + Returns + ------- + 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, + 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) + + # 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"] + + 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" + 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_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 + 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 + if all([x in dtypes for x in ts_case]): + divs = int((note_array[idx]["duration_div"] / note_array[idx]["duration_beat"]) / + (4 / note_array[idx]["ts_beat_type"] )) + 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"]]] + 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") + + # make sure there is a time signature from the beginning + global_time_sigs[0, 0] = 0 + + + + # Note id creation or re-assignment + 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='>> feature, names = make_note_feats(part, ['metrical_feature', 'articulation_feature']) @@ -72,9 +75,11 @@ 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, + force_fixed_size: bool = False, ) -> Tuple[np.ndarray, List]: """Compute the specified feature functions for a part. @@ -84,7 +89,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`, @@ -94,13 +99,21 @@ def make_note_features( Parameters ---------- - part : 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 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. + 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 ------- @@ -135,9 +148,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) + 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): @@ -300,10 +315,12 @@ 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( - part, + part : ScoreLike, include_pitch_spelling=False, include_key_signature=False, include_time_signature=False, @@ -381,6 +398,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 + 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 @@ -401,7 +422,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 +431,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 +447,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 +467,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: @@ -478,11 +499,11 @@ def grace_feature(na, part): 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 -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 +523,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 +567,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: + 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: + 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 +595,54 @@ def tempo_direction_feature(na, part): """ onsets = na["onset_div"] N = len(onsets) - + 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)) - 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: + 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 + 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 +651,22 @@ 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: + 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: + 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 +674,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 +698,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 force_size: + W = np.zeros((len(onsets), len(constant_names))) + names = constant_names + else: + 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: + j = names.index(name) + else: + names[j] = name W[:, j] = bf - names[j] = name return W, names @@ -683,7 +791,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 +819,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 +849,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 +866,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) if len(feature_by_name) > 0 else 1 + 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,27 +922,36 @@ 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) if len(feature_by_name) > 0 else 1 + 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)) + 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 @@ -837,7 +965,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 +980,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 +1020,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 +1029,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 +1048,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 +1087,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 +1110,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) diff --git a/partitura/musicanalysis/performance_codec.py b/partitura/musicanalysis/performance_codec.py index 58efbb7a..72416605 100644 --- a/partitura/musicanalysis/performance_codec.py +++ b/partitura/musicanalysis/performance_codec.py @@ -7,30 +7,23 @@ from typing import Union, Callable 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() +import warnings from partitura.score import Part, ScoreLike 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 __all__ = ["encode_performance", "decode_performance", "to_matched_score"] +#### Full Codecs #### + + @deprecated_alias(part='score', ppart="performance") def encode_performance( score: ScoreLike, @@ -38,7 +31,7 @@ def encode_performance( alignment: list, return_u_onset_idx=False, beat_normalization: str = "beat_period", # "beat_period_log", "beat_period_ratio", "beat_period_ratio_log", "beat_period_standardized" - tempo_smooth: Union[str, Callable] = "average" + tempo_smooth: Union[str, Callable] = "average" ): """ Encode expressive parameters from a matched performance @@ -55,10 +48,10 @@ def encode_performance( return_u_onset_idx : bool Return the indices of the unique score onsets beat_normalization : str (Optional) - Return extra columns for normalization parameters. + Return extra columns for normalization parameters. tempo_smooth : str (Optional) - How the tempo curve is computed. average or derivative. - Can also input a callable function for user-defined tempo curve. + How the tempo curve is computed. average or derivative. + Can also input a callable function for user-defined tempo curve. Returns ------- @@ -66,7 +59,7 @@ def encode_performance( A performance array with 4 fields: beat_period, velocity, timing, and articulation_log. If beat_normalization is defined as any method other than beat_period, - return the normalization value as extra columns in parameters. + return the normalization value as extra columns in parameters. snote_ids : dict A dict of snote_ids corresponding to performance notes. unique_onset_idxs : list (optional) @@ -203,10 +196,11 @@ def decode_performance( return ppart +#### Time and Articulation Codecs #### -def decode_time(score_onsets, - score_durations, - parameters, +def decode_time(score_onsets, + score_durations, + parameters, normalization = "beat_period", *args, **kwargs): """ @@ -225,8 +219,8 @@ def decode_time(score_onsets, unique_onset_idxs = score_info["unique_onset_idxs"] diff_u_onset_score = score_info["diff_u_onset"] - # reconstruct the time by the extra parameters, for testing the inversion. - # In practice, always reconstruct the time by beat_period. + # reconstruct the time by the extra parameters, for testing the inversion. + # In practice, always reconstruct the time by beat_period. if normalization != "beat_period": tempo_param_names = list(TEMPO_NORMALIZATION[normalization]['param_names']) time_param = np.array( @@ -263,6 +257,29 @@ 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 + articulation[idx] = np.log2(pd / (bp * sd)) + + return articulation + + def decode_articulation(score_durations, articulation_parameter, beat_period): """ Decode articulation @@ -306,14 +323,14 @@ def encode_tempo( performed_durations=performance[:, 1], return_onset_idxs=True, ) - elif tempo_smooth == "average": + elif tempo_smooth == "average": beat_period, s_onsets, unique_onset_idxs = tempo_by_average( score_onsets=score[:, 0], performed_onsets=performance[:, 0], score_durations=score[:, 1], performed_durations=performance[:, 1], return_onset_idxs=True, - ) + ) elif tempo_smooth == "derivative": beat_period, s_onsets, unique_onset_idxs = tempo_by_derivative( score_onsets=score[:, 0], @@ -438,7 +455,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 @@ -459,6 +476,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: @@ -541,7 +559,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', @@ -558,79 +576,12 @@ 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) +#### Alignment Processing #### - 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 @deprecated_alias(part='score', ppart="performance") -def to_matched_score(score: ScoreLike, - performance: PerformanceLike, +def to_matched_score(score: ScoreLike, + performance: PerformanceLike, alignment: list, include_score_markings=False): """ @@ -641,7 +592,7 @@ def to_matched_score(score: ScoreLike, score (score.ScoreLike): score information performance (performance.PerformanceLike): performance information alignment (List(Dict)): an alignment - include_score_markings (bool): include dynamcis and articulation + include_score_markings (bool): include dynamcis and articulation markings (Optional) Returns: @@ -656,9 +607,9 @@ def to_matched_score(score: ScoreLike, feature_functions = None if include_score_markings: - feature_functions = ["loudness_direction_feature", "articulation_feature", + feature_functions = ["loudness_direction_feature", "articulation_feature", "tempo_direction_feature", "slur_feature"] - + na = note_features.compute_note_array(score, feature_functions=feature_functions) p_na = performance.note_array() part_by_id = dict((n['id'], na[na['id'] == n['id']]) for n in na) @@ -670,7 +621,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] @@ -684,6 +634,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,80 +648,233 @@ 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 -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 ptime_to_stime_map, stime_to_ptime_map - return articulation +def get_matched_notes(spart_note_array, ppart_note_array, alignment): + """ + Get the indices of the matched notes in an alignment -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 - strict : bool - when True, return a strictly monotonic sequence (default: True) - deltas : + 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 ------- - ndarray - a monotonic sequence that has been linearly interpolated using a subset of s + 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 - _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] + 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: - _deltas = None - mask = np.ones(_s.shape[0], dtype=bool) - mask[0] = mask[-1] = False - idx = np.arange(_s.shape[0]) - s_mono = interp1d(idx[mask], _s[mask])(idx[1:-1]) - return _s[mask], _deltas[mask] + return unique_onset_idxs 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 +888,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 @@ -799,6 +901,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/musicanalysis/performance_features.py b/partitura/musicanalysis/performance_features.py new file mode 100644 index 00000000..5a4e7e98 --- /dev/null +++ b/partitura/musicanalysis/performance_features.py @@ -0,0 +1,496 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This module implement a series of mid-level descriptors of the performance expressions. +Built upon the low-level basis functions from the performance codec. +""" + +import sys +import types +from typing import Union, List +import warnings +import numpy as np +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, PerformedPart +from partitura.utils.generic import interp1d +from partitura.musicanalysis.performance_codec import to_matched_score, onsetwise_to_notewise, encode_tempo + + +__all__ = [ + "make_performance_features", +] + +# ordinal +OLS = ["ppp", "pp", "p", "mp", "mf", "f", "ff", "fff"] + + +class InvalidPerformanceFeatureException(Exception): + pass + +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 + + 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, pedal_feature + add_idx: bool + add score note idx column to feature array + + Returns + ------- + performance_features : structured array + """ + m_score, unique_onset_idxs, snote_ids = compute_matched_score(score, performance, alignment) + + acc = [] + if isinstance(feature_functions, str) and feature_functions == "all": + 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 + ) + ) + + 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, performance) + # 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.names)[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) + + 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) + full_performance_features = rfn.join_by("id", performance_features, m_score) + full_performance_features = full_performance_features.data + + 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 + + +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, 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", "id"], + [time_params['beat_period'], snote_ids], + ["f4", "U256"], usemask=False,) + return m_score, unique_onset_idxs, snote_ids + + +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_performance_features` function. For example: + + >>> feature, names = make_performance_features(score, + performance, + alignment, + ['asynchrony_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 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 + +### Asynchrony + +def asynchrony_feature(m_score: np.ndarray, + unique_onset_idxs: list, + performance: PerformanceLike): + """ + Compute the asynchrony 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 + + Returns + ------- + 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, 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 + """ + + 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 = min(onset_times.max() - onset_times.min(), 1) + 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] 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'] = 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] 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 = [] + 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'] = min(np.std(np.array(voices_onsets)), 1) + + return onsetwise_to_notewise(async_, unique_onset_idxs) + + +### Dynamics + + +### Articulation + +def articulation_feature(m_score : np.ndarray, + unique_onset_idxs: list, + performance: PerformanceLike, + 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. For normalization purposes we empirically cap the maximum to 5. + + References + ---------- + .. [1] B.Repp: Acoustics, Perception, and Production of Legato Articulation on a Digital Piano + + 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 + return_mask : bool + if true, return a boolean mask of legato notes, staccato notes and repeated notes + + Returns + ------- + 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_ = 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 _, 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) + + 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 + + if note_info['articulation'] == 'staccato': + mask[j]['staccato'] = True + + # 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): + """ + 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. + 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: + kor = 0 + + kor = kot / ioi + + 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 + + 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']) + + # 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_feature(m_score : list, + unique_onset_idxs: list, + performance: PerformanceLike): + """ + Compute the pedal features. + + 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 + + Returns + ------- + 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 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) + + x = np.linspace(0, 100, 200) + y = ramp_func(x) + + peaks, _ = find_peaks(-y, prominence=10) + peak_timepoints = x[peaks] + + 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) + + # 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] + + 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) + + # Filter out NaN values + W[np.isnan(W)] = 0.0 + + return np.array([tuple(i) for i in W], dtype=[("onset_value", "f4"), ("offset_value", "f4")]), agg_ramp_func + + +### Phrasing + +### Tempo diff --git a/partitura/score.py b/partitura/score.py index 4d811346..046d264f 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -282,6 +282,7 @@ def measure_number_map(self): """ # operations to avoid None values and filter them efficiently. m_it = self.measures + measures = np.array( [ [ @@ -2400,22 +2401,27 @@ class Measure(TimedObject): Parameters ---------- - number : int or None, optional - The number of the measure. 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 : intp + number : int + See parameters + name : str 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__()} number={self.number} name={self.name}" @property def page(self): @@ -3342,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/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/generic.py b/partitura/utils/generic.py index d4130a5f..3490d84e 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 @@ -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]: @@ -615,6 +615,43 @@ 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] + + 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(_x[mask], _s[mask], fill_value="extrapolate")(x_mono) + + return s_mono, x_mono + if __name__ == "__main__": import doctest diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 40161d6a..2d7bbd65 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -13,6 +13,19 @@ 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 + 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 @@ -729,7 +742,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: @@ -1194,7 +1207,7 @@ def compute_pianoroll( The `vertical_position_in_piano_roll` might be different from `original_midi_pitch` depending on the `pitch_margin` and `piano_range` arguments. - + Examples -------- @@ -1291,6 +1304,7 @@ def _make_pianoroll( onset, duration and (optionally) MIDI velocity information. See `compute_pianoroll` for a complete description of the arguments of this function. + """ # Get pitch, onset, offset from the note_info array @@ -3058,144 +3072,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), @@ -3343,9 +3219,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 +3236,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 +3250,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 @@ -3398,14 +3274,14 @@ 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: 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 @@ -3414,7 +3290,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: @@ -3441,6 +3317,39 @@ def slice_ppart_by_time( return ppart_slice +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=incomplete_bar_behaviour, part_voice_assign_mode = 4, minimum_ppq = 480 ) + midi = miditoolkit.MidiFile(temp_midi_path) + tokens = tokenizer(midi) + return tokens + + + if __name__ == "__main__": import doctest diff --git a/partitura/utils/normalize.py b/partitura/utils/normalize.py new file mode 100644 index 00000000..886bb9d3 --- /dev/null +++ b/partitura/utils/normalize.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This module contains normalization utilities +""" +import numpy as np + + +EPSILON=0.0001 + + +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) + # 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: + return array + + +def zero_one_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 of a vector from range [array.min(), array.max()] to [0, 1]. + Constant vector is clipped to [0, 1]. + """ + return range_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 = 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 + ---------- + 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. + """ + 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: + + # 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]["kwargs"]) + + return array diff --git a/setup.py b/setup.py index abcdaa8a..9d032b88 100644 --- a/setup.py +++ b/setup.py @@ -15,8 +15,8 @@ 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" -VERSION = "1.2.2" +REQUIRES_PYTHON = ">=3.7" +VERSION = "1.3.0" # What packages are required for this module to be executed? REQUIRED = ["numpy", "scipy", "lxml", "lark-parser", "xmlschema", "mido"] diff --git a/tests/__init__.py b/tests/__init__.py index 3c313ee8..c24bdcd0 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) @@ -135,6 +139,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) @@ -178,7 +189,15 @@ ] KERN_TIES = [os.path.join(KERN_PATH, fn) for fn in ["tie_mismatch.krn"]] - +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", + "test_note_ties.xml", + ] +] HARMONY_TESTFILES = [os.path.join(MUSICXML_PATH, fn) for fn in ["test_harmony.musicxml"]] MOZART_VARIATION_FILES = dict( @@ -203,3 +222,16 @@ ] 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"), + }, +] + +MIDIEXPORT_TESTFILES = [ + os.path.join(DATA_PATH, "musicxml", "test_anacrusis.xml") +] \ No newline at end of file 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..258af805 --- /dev/null +++ b/tests/data/match/Chopin_op10_no3_p01.match @@ -0,0 +1,3849 @@ +info(matchFileVersion,1.0.0). +info(piece,Chopin_op10_no3). +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). +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). +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/data/midi/mozart_k265_var1_quantized.mid b/tests/data/midi/mozart_k265_var1_quantized.mid new file mode 100644 index 00000000..63028d08 Binary files /dev/null and b/tests/data/midi/mozart_k265_var1_quantized.mid differ diff --git a/tests/data/midi/test_anacrusis.mid b/tests/data/midi/test_anacrusis.mid new file mode 100644 index 00000000..9a0522d1 Binary files /dev/null and b/tests/data/midi/test_anacrusis.mid differ 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/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 new file mode 100644 index 00000000..c535303a --- /dev/null +++ b/tests/data/musicxml/test_partial_measures_consecutive.xml @@ -0,0 +1,134 @@ + + + + + + MusicXML Part + + + + + + + 1 + + 0 + + + 1 + + G + 2 + + + + + C + 4 + + 2 + 1 + half + + + + C + 4 + + 2 + 1 + half + + + + + + + D + 4 + + 1 + 1 + half + + + + E + 4 + + 1 + 1 + half + + + + + + + F + 4 + + 2 + 1 + whole + + + + + + + G + 4 + + 4 + 1 + half + + + + + + + A + 4 + + 2 + 1 + half + + + + + + + B + -1 + 4 + + 1 + 1 + quarter + + + + + + + F + 4 + + 2 + 1 + whole + + + light-heavy + + + + 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_m21_import.py b/tests/test_m21_import.py new file mode 100644 index 00000000..472f8e0e --- /dev/null +++ b/tests/test_m21_import.py @@ -0,0 +1,134 @@ +""" +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 + +import music21 as m21 +import numpy as np + + +class TestImportM21(unittest.TestCase): + def test_grace_note(self): + m21_score = m21.converter.parse(M21_TESTFILES[1]) + 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]) + pt_score = load_music21(m21_score) + # test on part 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) + 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 = pt_score.parts[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) + + # 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"] + ) + ) + + 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"], + ) + ) + + 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"] + ) + ) diff --git a/tests/test_mei.py b/tests/test_mei.py index 3abf3459..3a854792 100644 --- a/tests/test_mei.py +++ b/tests/test_mei.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ -This module contains test functions for MEI export +This file contains test functions for MEI import """ import unittest diff --git a/tests/test_midi_export.py b/tests/test_midi_export.py index 077f6f8a..9efe92b8 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,39 @@ 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) + # 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) + + diff --git a/tests/test_note_array.py b/tests/test_note_array.py index 8941e1a2..3f6489d0 100644 --- a/tests/test_note_array.py +++ b/tests/test_note_array.py @@ -10,9 +10,10 @@ 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 +from tests import NOTE_ARRAY_TESTFILES, KERN_TESTFILES, METRICAL_POSITION_TESTFILES class TestNoteArray(unittest.TestCase): @@ -70,6 +71,88 @@ 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_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) + 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_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_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) 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") diff --git a/tests/test_partial_measures.py b/tests/test_partial_measures.py new file mode 100644 index 00000000..45f4a2e0 --- /dev/null +++ b/tests/test_partial_measures.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#%% +""" +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 numpy as np +from partitura.io import load_musicxml +from partitura.score import * + +from tests import MUSICXML_PARTIAL_MEASURES_TESTFILES + + +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 diff --git a/tests/test_performance_features.py b/tests/test_performance_features.py new file mode 100644 index 00000000..e0c4723a --- /dev/null +++ b/tests/test_performance_features.py @@ -0,0 +1,42 @@ +#!/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_EXPRESSIVE_FEATURES_TESTFILES +from partitura.musicanalysis.performance_features import make_performance_features +import os + + + +class TestPerformanceFeatures(unittest.TestCase): + def test_performance_features(self): + 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', '= (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(), + ) ) - ) + + 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): @@ -561,3 +600,46 @@ def test_interp1d(self): ) self.assertTrue(np.all(sinterp(x) == pinterp(x))) + +class TestTokenizer(unittest.TestCase): + def test_tokenize1(self): + """ Test the partitura tokenizer""" + tokenizer = miditok.MIDILike() + # produce tokens from the score with partitura + pt_score = partitura.load_score(TOKENIZER_TESTFILES[0]["score"]) + pt_tokens = tokenize(pt_score, tokenizer)[0].tokens + # produce tokens from the manually created MIDI file + mtok_midi = miditoolkit.MidiFile(TOKENIZER_TESTFILES[0]["midi"]) + mtok_tokens = tokenizer(mtok_midi)[0].tokens + # filter out velocity tokens + pt_tokens = [tok for tok in pt_tokens if not tok.startswith("Velocity")] + mtok_tokens = [tok for tok in mtok_tokens if not tok.startswith("Velocity")] + self.assertTrue(pt_tokens == mtok_tokens) + + def test_tokenize2(self): + """ Test the partitura tokenizer""" + tokenizer = miditok.REMI() + # produce tokens from the score with partitura + pt_score = partitura.load_score(TOKENIZER_TESTFILES[0]["score"]) + pt_tokens = tokenize(pt_score, tokenizer)[0].tokens + # produce tokens from the manually created MIDI file + mtok_midi = miditoolkit.MidiFile(TOKENIZER_TESTFILES[0]["midi"]) + mtok_tokens = tokenizer(mtok_midi)[0].tokens + # filter out velocity tokens + pt_tokens = [tok for tok in pt_tokens if not tok.startswith("Velocity")] + mtok_tokens = [tok for tok in mtok_tokens if not tok.startswith("Velocity")] + self.assertTrue(pt_tokens == mtok_tokens) + + def test_tokenize1(self): + """ Test the partitura tokenizer""" + tokenizer = miditok.MIDILike() + # produce tokens from the score with partitura + pt_score = partitura.load_score(TOKENIZER_TESTFILES[0]["score"]) + pt_tokens = tokenize(pt_score, tokenizer)[0].tokens + # produce tokens from the manually created MIDI file + mtok_midi = miditoolkit.MidiFile(TOKENIZER_TESTFILES[0]["midi"]) + mtok_tokens = tokenizer(mtok_midi)[0].tokens + # filter out velocity tokens + pt_tokens = [tok for tok in pt_tokens if not tok.startswith("Velocity")] + mtok_tokens = [tok for tok in mtok_tokens if not tok.startswith("Velocity")] + self.assertTrue(pt_tokens == mtok_tokens) \ No newline at end of file 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)