From a9ee5cd58880537d58e677371972c8c42ce880b3 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Tue, 18 Jul 2023 12:27:11 +0200 Subject: [PATCH 01/80] new class for performedNotes. --- partitura/performance.py | 98 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index f7ac86dc..aa2c8f15 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -85,7 +85,7 @@ def __init__( super().__init__() self.id = id self.part_name = part_name - self.notes = notes + self.notes = list(map(lambda n: PerformedNote(n), notes)) self.controls = controls or [] self.programs = programs or [] self.ppq = ppq @@ -203,7 +203,11 @@ def from_note_array( if "id" not in note_array.dtype.names: n_ids = ["n{0}".format(i) for i in range(len(note_array))] else: - n_ids = note_array["id"] + # Check if all ids are the same + if np.all(note_array["id"] == note_array["id"][0]): + n_ids = ["n{0}".format(i) for i in range(len(note_array))] + else: + n_ids = note_array["id"] if "track" not in note_array.dtype.names: tracks = np.zeros(len(note_array), dtype=int) @@ -280,6 +284,96 @@ def adjust_offsets_w_sustain( note["sound_off"] = offset +class PerformedNote(dict): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self["id"] = self.get("id", None) + self["pitch"] = self.get("pitch", self["midi_pitch"]) + self["note_on"] = self.get("note_on", -1) + self["note_off"] = self.get("note_off", -1) + self["sound_off"] = self.get("sound_off", self["note_off"]) + self["track"] = self.get("track", 0) + self["channel"] = self.get("channel", 1) + self["velocity"] = self.get("velocity", 60) + self.validate_values() + self._accepted_keys = ["id", "pitch", "note_on", "note_off", "velocity", "track", "channel", "sound_off"] + self.__setitem__ = self._setitem_new + + def __repr__(self): + return f"PerformedNote: {self['id']}" + + def __str__(self): + return f"PerformedNote: {self['id']}" + + def __eq__(self, other): + return self["id"] == other["id"] + + def __hash__(self): + return hash(self["id"]) + + def __lt__(self, other): + return self["note_on"] < other["note_on"] + + def __le__(self, other): + return self["note_on"] <= other["note_on"] + + def __gt__(self, other): + return self["note_on"] > other["note_on"] + + def __ge__(self, other): + return self["note_on"] >= other["note_on"] + + def __getitem__(self, key): + return self.get(key, None) + + def _setitem_new(self, key, value): + if key not in self._accepted_keys: + raise KeyError(f"Key {key} not in PerformedNote") + elif key == "note_off": + # Verify that the note_off is after the note_on + if value < self["note_on"]: + raise ValueError(f"note_off must be after note_on") + self["sound_off"] = value if self["sound_off"] < value else self["sound_off"] + self["note_off"] = value + elif key == "note_on": + # Verify that the note_on is before the note_off + if value > self["note_off"]: + raise ValueError(f"note_on must be before note_off") + + self["duration_sec"] = self["note_off"] - value + self["note_on"] = value + elif key == "sound_off": + # Verify that the sound_off is after the note_on + if value < self["note_off"]: + raise ValueError(f"sound_off must be after note_off") + self["sound_off"] = value + else: + self[key] = value + + def __delitem__(self, key): + raise KeyError("Cannot delete items from PerformedNote") + + def __iter__(self): + return iter(self.keys()) + + def __len__(self): + return len(self.keys()) + + def __contains__(self, key): + return key in self.keys() + + def validate_values(self): + if self["pitch"] > 127 or self["pitch"] < 0: + raise ValueError(f"pitch must be between 0 and 127") + if self["note_on"] < 0: + raise ValueError(f"Note on value provided is invalid, must be greater than or equal to 0") + if self["note_off"] < 0 or self["note_off"] < self["note_on"]: + raise ValueError(f"Note off value provided is invalid, " + f"must be greater than or equal to 0 and greater than note_on") + if self["velocity"] > 127 or self["velocity"] < 0: + raise ValueError(f"velocity must be between 0 and 127") + + class Performance(object): """Main object for representing a performance. From 966034e56a6ba60e5a1ab9dae76afd4346b46300 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Thu, 27 Jul 2023 12:58:57 +0200 Subject: [PATCH 02/80] add support for musescore 4 --- partitura/io/musescore.py | 74 ++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 2977b79e..8c6ccffb 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -35,38 +35,53 @@ class MuseScoreNotFoundException(Exception): class FileImportException(Exception): pass - -def find_musescore3(): - # # possible way to detect MuseScore... executable - # for p in os.environ['PATH'].split(':'): - # c = glob.glob(os.path.join(p, 'MuseScore*')) - # if c: - # print(c) - # break - - result = shutil.which("musescore") - - if result is None: - result = shutil.which("musescore3") - - if result is None: - result = shutil.which("mscore") - +def find_musescore_version(version=4): + """Find the path to the MuseScore executable for a specific version. + If version is a empty string it tries to find an unspecified version of + MuseScore which is used in some systems. + """ + result = shutil.which(f"musescore{version}") if result is None: - result = shutil.which("mscore3") - + result = shutil.which(f"mscore{version}") if result is None: if platform.system() == "Linux": pass - elif platform.system() == "Darwin": - result = shutil.which("/Applications/MuseScore 3.app/Contents/MacOS/mscore") - + result = shutil.which(f"/Applications/MuseScore {version}.app/Contents/MacOS/mscore") elif platform.system() == "Windows": - result = shutil.which(r"C:\Program Files\MuseScore 3\bin\MuseScore3.exe") + result = shutil.which(rf"C:\Program Files\MuseScore {version}\bin\MuseScore{version}.exe") return result +def find_musescore(): + """Find the path to the MuseScore executable. + + This function first tries to find the executable for MuseScore 4, + then for MuseScore 3, and finally for any version of MuseScore. + + Returns + ------- + str + Path to the MuseScore executable + + Raises + ------ + MuseScoreNotFoundException + When no MuseScore executable was found + """ + + mscore_exec = find_musescore(preferred_version=4) + if not mscore_exec: + mscore_exec = find_musescore(preferred_version=3) + if mscore_exec: + warnings.warn("Only Musescore 3 is installed. Consider upgrading to musescore 4.") + else: + mscore_exec = find_musescore(preferred_version="") + if mscore_exec: + warnings.warn("A unspecified version of MuseScore was found. Consider upgrading to musescore 4.") + else: + raise MuseScoreNotFoundException() + @deprecated_alias(fn="filename") @deprecated_parameter("ensure_list") @@ -103,12 +118,8 @@ def load_via_musescore( One or more part or partgroup objects """ - - mscore_exec = find_musescore3() - - if not mscore_exec: - raise MuseScoreNotFoundException() - + mscore_exec = find_musescore() + xml_fh = os.path.splitext(os.path.basename(filename))[0] + ".musicxml" cmd = [mscore_exec, "-o", xml_fh, filename] @@ -167,10 +178,7 @@ def render_musescore( out : Optional[PathLike] Path to the output generated image (or None if no image was generated) """ - mscore_exec = find_musescore3() - - if not mscore_exec: - return None + mscore_exec = find_musescore() if fmt not in ("png", "pdf"): warnings.warn("warning: unsupported output format") From 414e54e94c12316f5ba3f3ff0e817c57621ad322 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Thu, 27 Jul 2023 14:36:34 +0200 Subject: [PATCH 03/80] correct musescore import bug --- partitura/io/musescore.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 8c6ccffb..df6ff5bd 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -70,17 +70,18 @@ def find_musescore(): When no MuseScore executable was found """ - mscore_exec = find_musescore(preferred_version=4) + mscore_exec = find_musescore_version(version=4) if not mscore_exec: - mscore_exec = find_musescore(preferred_version=3) + mscore_exec = find_musescore_version(version=3) if mscore_exec: warnings.warn("Only Musescore 3 is installed. Consider upgrading to musescore 4.") else: - mscore_exec = find_musescore(preferred_version="") + mscore_exec = find_musescore_version(version="") if mscore_exec: warnings.warn("A unspecified version of MuseScore was found. Consider upgrading to musescore 4.") else: raise MuseScoreNotFoundException() + return mscore_exec @deprecated_alias(fn="filename") From eae4d4f4c24299f8b49419bd0048c3927a3a9c35 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Fri, 28 Jul 2023 10:56:20 +0200 Subject: [PATCH 04/80] added tests and corrected problem in kern import which was not throwing any exception for xml files --- partitura/io/importkern.py | 3 + tests/__init__.py | 5 + tests/data/musescore/160.03_Pastorale.mscx | 12823 +++++++++++++++++++ tests/data/musescore/mozart_k265_var1.mscz | Bin 0 -> 33081 bytes tests/test_musescore.py | 33 + 5 files changed, 12864 insertions(+) create mode 100644 tests/data/musescore/160.03_Pastorale.mscx create mode 100644 tests/data/musescore/mozart_k265_var1.mscz create mode 100644 tests/test_musescore.py diff --git a/partitura/io/importkern.py b/partitura/io/importkern.py index da0fecea..c48f6775 100644 --- a/partitura/io/importkern.py +++ b/partitura/io/importkern.py @@ -546,6 +546,9 @@ def parse_kern(kern_path: PathLike) -> np.ndarray: """ with open(kern_path, encoding="cp437") as file: lines = file.read().splitlines() + if lines[0][0] == "<": + # we are using a heuristic to stop the import if we are dealing with a XML file + raise Exception("Invalid Kern file") d = [line.split("\t") for line in lines if not line.startswith("!")] striped_parts = list() merge_index = [] diff --git a/tests/__init__.py b/tests/__init__.py index c24bdcd0..e1bed92f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -188,6 +188,11 @@ ] ] +MUSESCORE_TESTFILES = [ + os.path.join(DATA_PATH, "musescore", fn) + for fn in ["mozart_k265_var1.mscz", "160.03_Pastorale.mscx"] +] + KERN_TIES = [os.path.join(KERN_PATH, fn) for fn in ["tie_mismatch.krn"]] M21_TESTFILES = [ os.path.join(DATA_PATH, "musicxml", fn) diff --git a/tests/data/musescore/160.03_Pastorale.mscx b/tests/data/musescore/160.03_Pastorale.mscx new file mode 100644 index 00000000..d9af4478 --- /dev/null +++ b/tests/data/musescore/160.03_Pastorale.mscx @@ -0,0 +1,12823 @@ + + + 3.6.2 + 3224f34 + + + 0 + 480 + + 1 + 1 + 1 + 0 + https://imslp.org/wiki/Special:ReverseLookup/176824 + Adrian Nagel (2.1.1), Amelia Brey (2.3.0) + + 1855 + OxfordMusicOnline + 1848 + Franz Liszt + + 2019-01-26 + 2.3.0 + https://imslp.org/wiki/Ann%C3%A9es_de_p%C3%A8lerinage_I%2C_S.160_(Liszt%2C_Franz) + + 3 + Pastorale + 3.02 + https://musicbrainz.org/work/dd78778a-3d31-4ac0-9809-398bf0a2ddeb + mxl + Microsoft Windows + + JH, AB, AN + Tom Schreyer + http://musescore.com/user/2749876/scores/4094931 + + https://musescore.com/user/2749876 + https://viaf.org/viaf/179020308/ + https://www.wikidata.org/wiki/Q567462 + S.160 + Années de Pèlerinage, Première année: Suisse + + + + + + 1 + + + + + F + + Piano + + Piano + Pno. + Piano + keyboard.piano + + 100 + 100 + + + 100 + 33 + + + 100 + 50 + + + 100 + 67 + + + 100 + 100 + + + 120 + 67 + + + 120 + 100 + + + + + Fluid + + + + + Fluid + + + + + + 16.474 + 0 + + + 3. Pastorale + + + + Franz Liszt + + + + Années de Pèlerinage, Première année: Suisse, S.160 + + + + + + + + 1 + + + 4 + + + 12 + 8 + + + pp + 60 + + + + + Vivace + + + eighth + + + quarter + + + eighth + + + + + + + measure + 3/2 + + + + + + line + + + + pp + 60 + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + quarter + up + + 76 + 18 + + + + eighth + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + 0 + eighth + + + + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + line + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + quarter + up + + 68 + 22 + + + 76 + 18 + + + + + eighth + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + + 0 + 1 + quarter + + 0 + + + + + + + + measure + 3/2 + + + + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + line + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + quarter + up + + 76 + 18 + + + + eighth + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + 0 + eighth + + + + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + page + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + quarter + up + + 68 + 22 + + + 76 + 18 + + + + + eighth + + + double + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + + 0 + 1 + quarter + + 0 + + + + + + + + 6 + 8 + + + below + + un poco marcato + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 71 + 19 + + + + mid + eighth + + + accidentalSharp + + 70 + 24 + + + + eighth + + + + -9/32 + + + + + 66 + 20 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 71 + 19 + + + + mid + eighth + + + accidentalSharp + + 70 + 24 + + + + eighth + + + + -9/32 + + + + + 66 + 20 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 71 + 19 + + + + mid + eighth + + + accidentalSharp + + 70 + 24 + + + + eighth + + + + -9/32 + + + + + 66 + 20 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + line + + + + 1 + quarter + + articAccentAbove + + up + + + 1 + accidentalNatural + + 69 + 17 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + eighth + + + + -9/32 + + + + + 64 + 18 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + eighth + + + + -9/32 + + + + + 64 + 18 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + eighth + + + + -9/32 + + + + + 64 + 18 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 71 + 19 + + + + mid + eighth + + + accidentalSharp + + 70 + 24 + + + + eighth + + + + -9/32 + + + + + 66 + 20 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + line + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 71 + 19 + + + + mid + eighth + + + accidentalSharp + + 70 + 24 + + + + eighth + + + + -9/32 + + + + + 66 + 20 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 71 + 19 + + + + mid + eighth + + + accidentalSharp + + 70 + 24 + + + + eighth + + + + -9/32 + + + + + 66 + 20 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + + 1 + accidentalNatural + + 69 + 17 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + eighth + + + + -9/32 + + + + + 64 + 18 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + eighth + + + + -9/32 + + + + + 64 + 18 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + 3 + 4 + eighth + + + 4 + + 2 + + + up + + + begin + eighth + + + + + + 9/32 + + + + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + eighth + + + + -9/32 + + + + + 64 + 18 + + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + line + + + + quarter + + + half + + + double + + + + + + + 12 + 8 + + + measure + 3/2 + + + + + + + pp + 60 + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + line + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + quarter + up + + 76 + 18 + + + + eighth + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + 0 + eighth + + + + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + quarter + up + + 68 + 22 + + + 76 + 18 + + + + + eighth + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + + 0 + 1 + quarter + + 0 + + + + + + + page + + + + measure + 3/2 + + + + + + + ppp + 50 + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + + down + + + begin + eighth + + + up + + + + 1/4 + + + + + articStaccatoAbove + + + 83 + 19 + + + 87 + 23 + + + + mid + eighth + + articStaccatoAbove + + + 81 + 17 + + + 85 + 21 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + 83 + 19 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoAbove + + + 80 + 22 + + + + mid + eighth + + articStaccatoAbove + + + 76 + 18 + + + + eighth + + + + -1/4 + + + + + articStaccatoAbove + + + 71 + 19 + + + + quarter + up + + 76 + 18 + + + + eighth + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 73 + 21 + + + + down + + + begin + eighth + + articStaccatoBelow + + + 71 + 19 + + + + mid + eighth + + articStaccatoBelow + + + 68 + 22 + + + + eighth + + articStaccatoBelow + + + 63 + 23 + + + 69 + 17 + + + + quarter + down + + 64 + 18 + + + 68 + 22 + + + + 0 + eighth + + + + + + line + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 76 + 18 + + + + mid + eighth + + + + -1/8 + + + + + 71 + 19 + + + + eighth + + 71 + 19 + + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 68 + 22 + + + + eighth + down + + 68 + 22 + + + + + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + up + + + begin + eighth + + + + + + 1/8 + + + + + 81 + 17 + + + + mid + eighth + + + + -1/8 + + + + + 76 + 18 + + + + eighth + + 76 + 18 + + + + up + + + begin + eighth + + + + + + 1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + mid + eighth + + articStaccatoBelow + + + 69 + 17 + + + 78 + 20 + + + + eighth + + + + -1/4 + + + + + articStaccatoBelow + + + 71 + 19 + + + 80 + 22 + + + + quarter + up + + 68 + 22 + + + 76 + 18 + + + + + eighth + + + double + + + + + + 0 + 1 + quarter + + 0 + + + + quarter + down + + 73 + 21 + + + + eighth + down + + 69 + 17 + + + + + 0 + 1 + quarter + + 0 + + + + + 0 + 1 + quarter + + 0 + + + + + + + + 6 + 8 + + + below + + un poco marcato + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 71 + 19 + + + + mid + 16th + + + accidentalSharp + + 70 + 24 + + + + 16th + + + + -5/16 + + + + + 66 + 20 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + line + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + -1/16 + + + + 1 + + + + 1 + 1/16 + + + + + 1/16 + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 71 + 19 + + + + mid + 16th + + + accidentalSharp + + 70 + 24 + + + + 16th + + + + -5/16 + + + + + 66 + 20 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + + + -1 + -1/16 + + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 71 + 19 + + + + mid + 16th + + + accidentalSharp + + 70 + 24 + + + + 16th + + + + -5/16 + + + + + 66 + 20 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + + 1 + accidentalNatural + + 69 + 17 + + + + + 1 + + + + 2 + + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + line + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + + + -2 + + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + + 1 + + + + 2 + + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 71 + 19 + + + + mid + 16th + + + accidentalSharp + + 70 + 24 + + + + 16th + + + + -5/16 + + + + + 66 + 20 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 71 + 19 + + + + mid + 16th + + + accidentalSharp + + 70 + 24 + + + + 16th + + + + -5/16 + + + + + 66 + 20 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 71 + 19 + + + + + + -2 + + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 71 + 19 + + + + mid + 16th + + + accidentalSharp + + 70 + 24 + + + + 16th + + + + -5/16 + + + + + 66 + 20 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 61 + 21 + + + + + + + line + + + + 1 + quarter + + articAccentAbove + + up + + + 1 + accidentalNatural + + 69 + 17 + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + + 3 + + + + + 3 + -3/8 + + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + 1 + quarter + + articAccentAbove + + up + + 69 + 17 + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + + + down + + + begin + eighth + + + + + + 5/16 + + + + + 63 + 23 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 63 + 23 + + + + mid + 16th + + 59 + 19 + + + + 16th + + + + -5/16 + + + + + 63 + 23 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + up + + + begin + 16th + + + + + + 5/16 + + + + + 66 + 20 + + + + mid + 16th + + 68 + 22 + + + + mid + 16th + + 66 + 20 + + + + mid + 16th + + 69 + 17 + + + + mid + 16th + + 68 + 22 + + + + 16th + + + + -5/16 + + + + + 64 + 18 + + + + + + 1 + quarter + down + + 59 + 19 + + + + 1 + quarter + down + + 59 + 19 + + + + + + + + below + smorz. + + + + + -3 + 3/8 + + + + + up + + + begin + eighth + + + + + + 5/8 + + + + + 66 + 20 + + + + mid + eighth + + 68 + 22 + + + + mid + eighth + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + eighth + + + + -5/8 + + + + + 64 + 18 + + + + + + 1 + half + down + + 59 + 19 + + + + + + + + ritenuto + + + up + + + begin + eighth + + + + + + 5/8 + + + + + 66 + 20 + + + + mid + eighth + + 68 + 22 + + + + mid + eighth + + 66 + 20 + + + + mid + eighth + + 69 + 17 + + + + mid + eighth + + 68 + 22 + + + + fermataAbove + + + eighth + + + + -5/8 + + + + + 64 + 18 + + + + end + + + + + 1 + half + down + + 59 + 19 + + + + + + + + + + 4 + + + 12 + 8 + + + below + + con + + + 1 + E.V + + + eighth + + + + + + 3/8 + + + + up + + + keyboardPedalPed + + + 59 + 19 + + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + + + + + 1 + I + + + quarter + up + + 40 + 18 + + + + eighth + + + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 40 + 18 + + + + eighth + + + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + + + + + 1 + I[V{ + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + 1 + V + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I|PAC} + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + 1 + { + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 9/8 + + + + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + + 52 + 18 + + + + eighth + + 61 + 21 + + + + 1 + I + + + quarter + + 52 + 18 + + + + 1 + V(6) + + + eighth + + + accidentalSharp + + 63 + 23 + + + + 1 + I + + + quarter + + 52 + 18 + + + + eighth + + + + -9/8 + + + + + 59 + 19 + + + 64 + 18 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 7/8 + + + + up + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 61 + 21 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V(6) + + + eighth + up + + + accidentalSharp + + 63 + 23 + + + + 1 + I|PAC} + + + quarter + + + + -7/8 + + + + up + + 52 + 18 + + + 59 + 19 + + + + eighth + down + + 47 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + 1 + I] + + + quarter + up + + 40 + 18 + + + + eighth + + + up + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 40 + 18 + + + + eighth + + + up + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + + + + + 1 + I[V{ + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + 1 + V + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I|PAC} + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 9/8 + + + + up + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 61 + 21 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V(6) + + + eighth + up + + + accidentalSharp + + 63 + 23 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + 64 + 18 + + + + + + 1 + { + + + 1 + whole + + 40 + 18 + + + + + + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 7/8 + + + + up + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 61 + 21 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V(6) + + + eighth + up + + + accidentalSharp + + 63 + 23 + + + + 1 + I]|PAC} + + + quarter + + + + -7/8 + + + + up + + 52 + 18 + + + 59 + 19 + + + + + eighth + + + double + + + + + 1 + whole + + 40 + 18 + + + + + + + + 6 + 8 + + + 1 + V.I6{ + + + eighth + + + 1 + I[I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + V7/IV] + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(94) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -3/16 + + + 1 + IV + + + + + + + 1 + V7/IV + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(94) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -3/16 + + + 1 + IV + + + + + + + 1 + V7/IV + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(94) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -3/16 + + + 1 + IV + + + + + + + 1 + I6 + + + eighth + + + 1 + I[I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + V7/IV] + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(94) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -3/16 + + + 1 + IV + + + + + + + 1 + V7/IV + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(94) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -3/16 + + + 1 + IV + + + + + + + 1 + V7/IV + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(94) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -3/16 + + + 1 + IV + + + + + + + quarter + + + pp + 60 + above + + + + 1 + I.V + + + eighth + + + + + + 3/8 + + + + up + + 59 + 19 + + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + double + + + + + + + 12 + 8 + + + 1 + I|PAC} + + + quarter + up + + 40 + 18 + + + + eighth + + + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 40 + 18 + + + + eighth + + + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + + + + + 1 + I[V{ + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + 1 + V + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I|PAC} + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 9/8 + + + + up + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 61 + 21 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V(6) + + + eighth + up + + + accidentalSharp + + 63 + 23 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + 64 + 18 + + + + + + 1 + { + + + 1 + whole + + 40 + 18 + + + + + + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 7/8 + + + + up + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 61 + 21 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V(6) + + + eighth + up + + + accidentalSharp + + 63 + 23 + + + + 1 + I|PAC} + + + quarter + + + + -7/8 + + + + up + + 52 + 18 + + + 59 + 19 + + + + eighth + down + + 47 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + 1 + I] + + + quarter + up + + 40 + 18 + + + + eighth + + + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 40 + 18 + + + + eighth + + + + + + 3/8 + + + + up + + 59 + 19 + + + + 1 + V + + + quarter + up + + 47 + 19 + + + + eighth + + + + -3/8 + + + + up + + 59 + 19 + + + + + + + + 1 + I[V{ + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + 1 + V + + + quarter + + + -1/8 + + + 1 + IV + + + 1/8 + + + 1 + I + + + eighth + + + + + + 9/8 + + + + up + + 59 + 19 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 59 + 19 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V7 + + + eighth + up + + 59 + 19 + + + + 1 + I|PAC} + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + + + + 1 + whole + + 40 + 18 + + + + + + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 9/8 + + + + up + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 61 + 21 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V(6) + + + eighth + up + + + accidentalSharp + + 63 + 23 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + eighth + + + + -9/8 + + + + up + + 59 + 19 + + + 64 + 18 + + + + + + 1 + { + + + 1 + whole + + 40 + 18 + + + + + + + + quarter + + + 1 + V7/IV + + + eighth + + + + + + 7/8 + + + + up + + + accidentalNatural + + 62 + 16 + + + + 1 + IV + + + quarter + up + + 52 + 18 + + + + eighth + up + + 61 + 21 + + + + 1 + I + + + quarter + up + + 52 + 18 + + + + 1 + V(6) + + + eighth + up + + + accidentalSharp + + 63 + 23 + + + + 1 + I]|PAC} + + + quarter + + + + -7/8 + + + + up + + 52 + 18 + + + 59 + 19 + + + + eighth + + + double + + + + + 1 + whole + + 40 + 18 + + + + + + + + 6 + 8 + + + 1 + V.I6{ + + + eighth + + + 1 + I[I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + V7/IV] + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(9) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + IV + + + + + + + 1 + V7/IV + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(9) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + IV + + + + + + + 1 + V7/IV + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(9) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + IV + + + + + + + 1 + I6 + + + eighth + + + 1 + I[I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + I6 + + + eighth + + + 1 + I + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + V + + + 1 + quarter + + articAccentAbove + + down + + 47 + 19 + + + 54 + 20 + + + + + + + + 1 + V7/IV] + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(9) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + IV + + + + + + + 1 + V7/IV + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + IV(9) + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + IV + + + + + + + 1 + I.V7 + + + eighth + + + down + + + begin + eighth + + 47 + 19 + + + 54 + 20 + + + + eighth + + 47 + 19 + + + 54 + 20 + + + + 1 + I(9)|IAC} + + + 1 + quarter + + articAccentBelow + + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + I + + + + + + + 1 + I(9) + + + 1 + quarter + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + I + + + 1/8 + + + 1 + I(9) + + + 1 + quarter + up + + 40 + 18 + + + 47 + 19 + + + + -1/8 + + + 1 + I + + + + + + + ppp + 45 + above + + + + 1 + I(9) + + + 1 + half + up + + 40 + 18 + + + 47 + 19 + + + + -3/8 + + + 1 + I(4) + + + 1/8 + + + 1 + I + + + + + + + 1 + I(9) + + + fermataAbove + + + 1 + half + up + + 40 + 18 + + + 47 + 19 + + + + -3/8 + + + 1 + I(4) + + + 1/8 + + + 1 + I + + + 1/4 + + + end + + + + + + diff --git a/tests/data/musescore/mozart_k265_var1.mscz b/tests/data/musescore/mozart_k265_var1.mscz new file mode 100644 index 0000000000000000000000000000000000000000..54a96f328416853774c727c61e7c7e410927a301 GIT binary patch literal 33081 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 literal 0 HcmV?d00001 diff --git a/tests/test_musescore.py b/tests/test_musescore.py new file mode 100644 index 00000000..2cb44eb8 --- /dev/null +++ b/tests/test_musescore.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +This file contains test functions for MEI import +""" + +import unittest + +from tests import MUSESCORE_TESTFILES +from partitura import load_musicxml, load_mei, EXAMPLE_MEI +import partitura.score as score +from partitura.io.importmei import MeiParser +from partitura.utils import compute_pianoroll +from lxml import etree +from xmlschema.names import XML_NAMESPACE +from partitura.io import load_score, load_via_musescore + +import numpy as np +from pathlib import Path + + +class TestImportMusescore(unittest.TestCase): + def test_number_of_parts1(self): + score = load_via_musescore(MUSESCORE_TESTFILES[0]) + self.assertTrue(len(score.parts) == 1) + self.assertTrue(len(score.note_array()) == 218) + + def test_epfl_scores(self): + score = load_via_musescore(MUSESCORE_TESTFILES[1]) + self.assertTrue(len(score.parts) == 1) + # try the generic loading function + score = load_score(MUSESCORE_TESTFILES[1]) + self.assertTrue(len(score.parts) == 1) \ No newline at end of file From 660b748fdf665d26eb8573ae486c1e612a2c287a Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 28 Jul 2023 11:30:20 +0200 Subject: [PATCH 05/80] update workflow: Installing Musescore dependency only for linux runner. The tests should be accordingly adapted. --- .github/workflows/partitura_unittests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index 8f3efe44..d46561c9 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -27,6 +27,8 @@ jobs: pip install . - name: Install Optional dependencies run: | + if [ "$RUNNER_OS" == "Linux" ]; then + sudo apt-get install musescore 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 From 35eba7a294ef57f0f04392bfbb79738e93438e57 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Fri, 28 Jul 2023 11:32:27 +0200 Subject: [PATCH 06/80] added skiptest for github actions --- tests/test_musescore.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/test_musescore.py b/tests/test_musescore.py index 2cb44eb8..e99728b4 100644 --- a/tests/test_musescore.py +++ b/tests/test_musescore.py @@ -14,6 +14,7 @@ from lxml import etree from xmlschema.names import XML_NAMESPACE from partitura.io import load_score, load_via_musescore +import platform import numpy as np from pathlib import Path @@ -21,13 +22,21 @@ class TestImportMusescore(unittest.TestCase): def test_number_of_parts1(self): - score = load_via_musescore(MUSESCORE_TESTFILES[0]) - self.assertTrue(len(score.parts) == 1) - self.assertTrue(len(score.note_array()) == 218) + # dirty trick, since we can install Musescore only on linux environment in github actions + if platform.system() == "Linux": + score = load_via_musescore(MUSESCORE_TESTFILES[0]) + self.assertTrue(len(score.parts) == 1) + self.assertTrue(len(score.note_array()) == 218) + else: + self.skipTest("MuseScore test can't run on non-linux environment in github actions") def test_epfl_scores(self): - score = load_via_musescore(MUSESCORE_TESTFILES[1]) - self.assertTrue(len(score.parts) == 1) - # try the generic loading function - score = load_score(MUSESCORE_TESTFILES[1]) - self.assertTrue(len(score.parts) == 1) \ No newline at end of file + # dirty trick, since we can install Musescore only on linux environment in github actions + if platform.system() == "Linux": + score = load_via_musescore(MUSESCORE_TESTFILES[1]) + self.assertTrue(len(score.parts) == 1) + # try the generic loading function + score = load_score(MUSESCORE_TESTFILES[1]) + self.assertTrue(len(score.parts) == 1) + else: + self.skipTest("MuseScore test can't run on non-linux environment in github actions") \ No newline at end of file From 333c8b951b243027287e3d46cbde7e0063f34fa1 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 28 Jul 2023 11:35:01 +0200 Subject: [PATCH 07/80] update workflow: Installing Musescore dependency only for linux runner. The tests should be accordingly adapted. --- .github/workflows/partitura_unittests.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index d46561c9..d271d185 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -25,10 +25,16 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . - - name: Install Optional dependencies + - name: Install Musescore run: | - if [ "$RUNNER_OS" == "Linux" ]; then + if matrix.os == 'ubuntu-latest': sudo apt-get install musescore +# elif matrix.os == 'macos-latest': +# brew install musescore +# elif matrix.os == 'windows-latest': +# choco install musescore + - 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 From eff27f38f647707f2a3dac73d1e8ed31a31a0fc9 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 28 Jul 2023 11:38:08 +0200 Subject: [PATCH 08/80] Attempt to fix syntax error on workflow file. --- .github/workflows/partitura_unittests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index d271d185..e804ca2e 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -27,12 +27,13 @@ jobs: pip install . - name: Install Musescore run: | - if matrix.os == 'ubuntu-latest': + if [ "$RUNNER_OS" == "Linux" ]; then sudo apt-get install musescore # elif matrix.os == 'macos-latest': # brew install musescore # elif matrix.os == 'windows-latest': # choco install musescore + shell: bash - name: Install Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 From 3488684e80a43f7adef707e0000621c10780dd25 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 28 Jul 2023 11:41:04 +0200 Subject: [PATCH 09/80] Third Attempt to fix syntax error on workflow file. --- .github/workflows/partitura_unittests.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index e804ca2e..b4feab90 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -25,15 +25,10 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . - - name: Install Musescore + - if: matrix.platform == 'ubuntu-latest' + name: Install Musescore run: | - if [ "$RUNNER_OS" == "Linux" ]; then - sudo apt-get install musescore -# elif matrix.os == 'macos-latest': -# brew install musescore -# elif matrix.os == 'windows-latest': -# choco install musescore - shell: bash + sudo apt-get install musescore - name: Install Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 From 6b33d9ac0f0dd36f98d742b3b088e32217f0ea06 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Fri, 28 Jul 2023 15:33:31 +0200 Subject: [PATCH 10/80] better function naming in test_load_score test. Stop condition for bad MEI parsing --- partitura/io/musescore.py | 8 ++++++++ tests/test_load_score.py | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index df6ff5bd..223d2e90 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -119,6 +119,14 @@ def load_via_musescore( One or more part or partgroup objects """ + # open the file as text and check if the first symbol is "<" to avoid + # further processing in case of non-XML files + with open(filename, "r") as f: + if f.read(1) != "<": + raise FileImportException( + "File {} is not a valid XML file.".format(filename) + ) + mscore_exec = find_musescore() xml_fh = os.path.splitext(os.path.basename(filename))[0] + ".musicxml" diff --git a/tests/test_load_score.py b/tests/test_load_score.py index e7b3c8f5..3ae02011 100644 --- a/tests/test_load_score.py +++ b/tests/test_load_score.py @@ -37,9 +37,9 @@ def test_load_score(self): + MATCH_IMPORT_EXPORT_TESTFILES + EXAMPLE_FILES ): - self.load_score(fn) + self.check_return_type(fn) - def load_score(self, fn): + def check_return_type(self, fn): try: score = load_score(fn) self.assertTrue(isinstance(score, Score)) @@ -49,4 +49,4 @@ def load_score(self, fn): for pp in score.parts: self.assertTrue(isinstance(pp, Part)) except NotSupportedFormatError: - self.assertTrue(False) + self.assertTrue(False, f"Score {fn} failing when parsed.") From 3befc24bdffeb1dd6839e57cdcd9a5b00b6f9b32 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Tue, 1 Aug 2023 18:20:07 +0200 Subject: [PATCH 11/80] some MEI bugfixes --- partitura/io/importmei.py | 86 +++++++++++++++++++++++++++++++++++---- partitura/utils/music.py | 2 + 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 4b012ddd..29c7bbbd 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -3,6 +3,7 @@ """ This module contains methods for importing MEI files. """ +from collections import OrderedDict from lxml import etree from xmlschema.names import XML_NAMESPACE import partitura.score as score @@ -68,6 +69,11 @@ def __init__(self, mei_path: PathLike) -> None: self.parts = ( None # parts get initialized in create_parts() and filled in fill_parts() ) + # find the music tag inside the document + music_el = self.document.findall(self._ns_name("music", all=True)) + if len(music_el) != 1: + raise Exception("Only MEI with a single music element are supported") + self.music_el = music_el[0] self.repetitions = ( [] ) # to be filled when we encounter repetitions and process in the end @@ -78,12 +84,12 @@ def __init__(self, mei_path: PathLike) -> None: def create_parts(self): # handle main scoreDef info: create the part list - main_partgroup_el = self.document.find(self._ns_name("staffGrp", all=True)) + main_partgroup_el = self.music_el.find(self._ns_name("staffGrp", all=True)) self.parts = self._handle_main_staff_group(main_partgroup_el) def fill_parts(self): # fill parts with the content of the score - scores_el = self.document.findall(self._ns_name("score", all=True)) + scores_el = self.music_el.findall(self._ns_name("score", all=True)) if len(scores_el) != 1: raise Exception("Only MEI with a single score element are supported") sections_el = scores_el[0].findall(self._ns_name("section")) @@ -366,7 +372,7 @@ def _intsymdur_from_symbolic(self, symbolic_dur): def _find_ppq(self): """Finds the ppq for MEI filed that do not explicitely encode this information""" - els_with_dur = self.document.xpath(".//*[@dur]") + els_with_dur = self.music_el.xpath(".//*[@dur]") durs = [] durs_ppq = [] for el in els_with_dur: @@ -728,6 +734,53 @@ def _handle_mrest(self, mrest_el, position, voice, staff, part): ) # add mrest to the part part.add(rest, position, position + parts_per_measure) + # return duration to update the position in the layer + return position + parts_per_measure + + def _handle_multirest(self, multirest_el, position, voice, staff, part): + """ + Handles a rest that spawn multiple measures + + Parameters + ---------- + multirest_el : lxml tree + A mrest element in the lxml tree. + position : int + The current position on the timeline. + voice : int + The voice of the section. + staff : int + The current staff also refers to a Part. + part : Partitura.Part + The created part to add elements to. + + Returns + ------- + position + duration : int + Next position on the timeline. + """ + # find id + multirest_id = multirest_el.attrib[self._ns_name("id", XML_NAMESPACE)] + # find how many measures + n_measures = int(multirest_el.attrib["num"]) + if n_measures > 1: + raise Exception(f"Multi-rests with more than 1 measure are not supported yet. Found one with {n_measures}.") + # find closest time signature + last_ts = list(part.iter_all(cls=score.TimeSignature))[-1] + # find divs per measure + ppq = part.quarter_duration_map(position) + parts_per_measure = int(ppq * 4 * last_ts.beats / last_ts.beat_type) + + # create dummy rest to insert in the timeline + rest = score.Rest( + id=multirest_id, + voice=voice, + staff=1, + symbolic_duration=estimate_symbolic_duration(parts_per_measure, ppq), + articulations=None, + ) + # add mrest to the part + part.add(rest, position, position + parts_per_measure) # now iterate # return duration to update the position in the layer return position + parts_per_measure @@ -780,7 +833,18 @@ def _handle_chord(self, chord_el, position, voice, staff, part): def _handle_space(self, e, position, part): """Moves current position.""" - space_id, duration, symbolic_duration = self._duration_info(e, part) + try: + space_id, duration, symbolic_duration = self._duration_info(e, part) + except KeyError: # if the space don't have a duration, move to the end of the measure + # find closest time signature + last_ts = list(part.iter_all(cls=score.TimeSignature))[-1] + # find divs per measure + ppq = part.quarter_duration_map(position) + parts_per_measure = int(ppq * 4 * last_ts.beats / last_ts.beat_type) + # find divs elapsed since last barline + last_barline = list(part.iter_all(cls=pt.score.Measure))[-1] + duration = position - last_barline.start.t + return position + duration def _handle_barline_symbols(self, measure_el, position: int, left_or_right: str): @@ -823,6 +887,10 @@ def _handle_layer_in_staff_in_measure( new_position = self._handle_mrest( e, position, ind_layer, ind_staff, part ) + elif e.tag == self._ns_name("multiRest"): # rest that spawn more than one measure + new_position = self._handle_multirest( + e, position, ind_layer, ind_staff, part + ) elif e.tag == self._ns_name("beam"): # TODO : add Beam element # recursive call to the elements inside beam @@ -967,8 +1035,7 @@ def _handle_section(self, section_el, parts, position: int): if last_measure.end.t != max_position: part.add( pt.score.Measure(number=last_measure.number), - position, - max_position, + max_position ) part.remove(last_measure) position = max_position @@ -1024,7 +1091,7 @@ def _tie_notes(self, section_el, part_list): all_notes = [ note for part in score.iter_parts(part_list) - for note in part.iter_all(cls=score.Note) + for note in part.iter_all(cls=score.Note, include_subclasses=True) ] all_notes_dict = {note.id: note for note in all_notes} for tie_el in ties_el: @@ -1052,6 +1119,7 @@ def _insert_repetitions(self): "WARNING : unmatched repetitions. adding a repetition start at position 0" ) self.repetitions.insert(0, {"type": "start", "pos": 0}) + status = "stop" sanitized_repetition_list = [] # check if start-stop are alternate @@ -1082,11 +1150,13 @@ def _insert_repetitions(self): # check if ending with a start if sanitized_repetition_list[-1] == "start": print("WARNING : unmatched repetitions. Ignoring last start") + ## sanitize the found repetitions to remove duplicates + sanitized_repetition_list = list(OrderedDict((tuple(d.items()), d) for d in sanitized_repetition_list).values()) self.repetitions = sanitized_repetition_list ## insert the repetitions to all parts for rep_start, rep_stop in zip(self.repetitions[:-1:2], self.repetitions[1::2]): - assert rep_start["type"] == "start" and rep_stop["type"] == "stop" + assert rep_start["type"] == "start" and rep_stop["type"] == "stop", "Something wrong with repetitions" for part in score.iter_parts(self.parts): part.add(score.Repeat(), rep_start["pos"], rep_stop["pos"]) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index a1469e50..dfb3fdef 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -686,6 +686,8 @@ def midi_ticks_to_seconds( SIGN_TO_ALTER = { "n": 0, + "ns" : 1, + "nf" : -1, "#": 1, "s": 1, "ss": 2, From dc017649c64e45417c859341a5bd52f1ed87b99c Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 2 Aug 2023 14:52:19 +0200 Subject: [PATCH 12/80] Deleted 4 useless test files, bugfixes on MEI import with strange tuplets --- partitura/io/importmei.py | 28 +++++--- tests/__init__.py | 5 +- tests/data/mei/example_noMeasures_noBeams.mei | 53 --------------- .../data/mei/example_noMeasures_withBeams.mei | 55 --------------- .../data/mei/example_withMeasures_noBeams.mei | 51 -------------- .../mei/example_withMeasures_withBeams.mei | 53 --------------- tests/data/mei/test_divs_tuplet.mei | 67 +++++++++++++++++++ tests/temp_test.py | 4 ++ tests/test_mei.py | 52 +++++++------- 9 files changed, 119 insertions(+), 249 deletions(-) delete mode 100644 tests/data/mei/example_noMeasures_noBeams.mei delete mode 100644 tests/data/mei/example_noMeasures_withBeams.mei delete mode 100644 tests/data/mei/example_withMeasures_noBeams.mei delete mode 100644 tests/data/mei/example_withMeasures_withBeams.mei create mode 100644 tests/data/mei/test_divs_tuplet.mei create mode 100644 tests/temp_test.py diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 29c7bbbd..615df845 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -5,6 +5,7 @@ """ from collections import OrderedDict from lxml import etree +from fractions import Fraction from xmlschema.names import XML_NAMESPACE import partitura.score as score from partitura.utils.music import ( @@ -357,18 +358,21 @@ def _handle_staffdef(self, staffdef_el, position, part): self._handle_clef(staffdef_el, position, part) def _intsymdur_from_symbolic(self, symbolic_dur): - """Produce a int symbolic dur (e.g. 12 is a eight note triplet) and a dot number by looking at the symbolic dur dictionary: - i.e., symbol, eventual tuplet ancestors.""" + """Produce a int symbolic dur (e.g. 8 is a eight note), a dot number, and a tuplet modifier, + e.g., (2,3) means there are 3 notes in the space of 2 notes.""" intsymdur = SYMBOLIC_TO_INT_DURS[symbolic_dur["type"]] # deals with tuplets if symbolic_dur.get("actual_notes") is not None: assert symbolic_dur.get("normal_notes") is not None - intsymdur = ( - intsymdur * symbolic_dur["actual_notes"] / symbolic_dur["normal_notes"] - ) + # intsymdur = ( + # intsymdur * symbolic_dur["actual_notes"] / symbolic_dur["normal_notes"] + # ) + tuplet_modifier = (symbolic_dur["normal_notes"], symbolic_dur["actual_notes"]) + else: + tuplet_modifier = None # deals with dots dots = symbolic_dur.get("dots") if symbolic_dur.get("dots") is not None else 0 - return intsymdur, dots + return intsymdur, dots, tuplet_modifier def _find_ppq(self): """Finds the ppq for MEI filed that do not explicitely encode this information""" @@ -377,7 +381,11 @@ def _find_ppq(self): durs_ppq = [] for el in els_with_dur: symbolic_duration = self._get_symbolic_duration(el) - intsymdur, dots = self._intsymdur_from_symbolic(symbolic_duration) + intsymdur, dots, tuplet_mod = self._intsymdur_from_symbolic(symbolic_duration) + if tuplet_mod is not None: + # consider time modifications keeping the numerator of the minimized fraction + minimized_fraction = Fraction(intsymdur * tuplet_mod[1], tuplet_mod[0]) + intsymdur = minimized_fraction.numerator # double the value if we have dots, to be sure be able to encode that with integers in partitura durs.append(intsymdur * (2**dots)) durs_ppq.append( @@ -580,9 +588,11 @@ def _duration_info(self, el, part): duration = 0 if el.get("grace") is not None else int(el.get("dur.ppq")) else: # compute the duration from the symbolic duration - intsymdur, dots = self._intsymdur_from_symbolic(symbolic_duration) + intsymdur, dots, tuplet_mod = self._intsymdur_from_symbolic(symbolic_duration) divs = part._quarter_durations[0] # divs is the same as ppq - duration = divs * 4 / intsymdur + if tuplet_mod is None: + tuplet_mod = (1,1) # if no tuplet modifier, set one that does not change the duration + duration = (divs * 4 * tuplet_mod[0]) / (intsymdur * tuplet_mod[1]) for d in range(dots): duration = duration + 0.5 * duration # sanity check to verify the divs are correctly set diff --git a/tests/__init__.py b/tests/__init__.py index e1bed92f..2a3dad52 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -150,10 +150,6 @@ MEI_TESTFILES = [ os.path.join(MEI_PATH, fn) for fn in [ - "example_noMeasures_noBeams.mei", - "example_noMeasures_withBeams.mei", - "example_withMeasures_noBeams.mei", - "example_withMeasures_withBeams.mei", "Bach_Prelude.mei", "Schubert_An_die_Sonne_D.439.mei", "test_tuplets.mei", @@ -171,6 +167,7 @@ "test_articulation.mei", "test_merge_voices2.mei", "CRIM_Mass_0030_4.mei", + "test_divs_tuplet.mei" ] ] diff --git a/tests/data/mei/example_noMeasures_noBeams.mei b/tests/data/mei/example_noMeasures_noBeams.mei deleted file mode 100644 index 3b89444d..00000000 --- a/tests/data/mei/example_noMeasures_noBeams.mei +++ /dev/null @@ -1,53 +0,0 @@ - - - - - TEST - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-
diff --git a/tests/data/mei/example_noMeasures_withBeams.mei b/tests/data/mei/example_noMeasures_withBeams.mei deleted file mode 100644 index 7619a276..00000000 --- a/tests/data/mei/example_noMeasures_withBeams.mei +++ /dev/null @@ -1,55 +0,0 @@ - - - - - TEST - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-
diff --git a/tests/data/mei/example_withMeasures_noBeams.mei b/tests/data/mei/example_withMeasures_noBeams.mei deleted file mode 100644 index cd9a1e98..00000000 --- a/tests/data/mei/example_withMeasures_noBeams.mei +++ /dev/null @@ -1,51 +0,0 @@ - - - - - TEST - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-
diff --git a/tests/data/mei/example_withMeasures_withBeams.mei b/tests/data/mei/example_withMeasures_withBeams.mei deleted file mode 100644 index 3b6b9f03..00000000 --- a/tests/data/mei/example_withMeasures_withBeams.mei +++ /dev/null @@ -1,53 +0,0 @@ - - - - - TEST - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-
diff --git a/tests/data/mei/test_divs_tuplet.mei b/tests/data/mei/test_divs_tuplet.mei new file mode 100644 index 00000000..68fc1785 --- /dev/null +++ b/tests/data/mei/test_divs_tuplet.mei @@ -0,0 +1,67 @@ + + + + + + + + Untitled score + + Composer / arranger + + + + 2023-08-02 + + + + + + Verovio +

Transcoded from MusicXML

+
+
+
+
+ + + + + + + + + Picc. + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
diff --git a/tests/temp_test.py b/tests/temp_test.py new file mode 100644 index 00000000..219c22c3 --- /dev/null +++ b/tests/temp_test.py @@ -0,0 +1,4 @@ +import partitura as pt + +score = pt.load_mei(r"C:\Users\fosca\Desktop\JKU\partitura\tests\data\mei\example_noMeasures_noBeams.mei") +print(score.parts) diff --git a/tests/test_mei.py b/tests/test_mei.py index 3a854792..1564504a 100644 --- a/tests/test_mei.py +++ b/tests/test_mei.py @@ -33,7 +33,7 @@ class TestImportMEI(unittest.TestCase): def test_main_part_group1(self): - parser = MeiParser(MEI_TESTFILES[5]) + parser = MeiParser(MEI_TESTFILES[1]) main_partgroup_el = parser.document.find(parser._ns_name("staffGrp", all=True)) part_list = parser._handle_main_staff_group(main_partgroup_el) self.assertTrue(len(part_list) == 2) @@ -65,14 +65,14 @@ def test_main_part_group1(self): self.assertTrue(part_list[1].id == "P5") def test_main_part_group2(self): - parser = MeiParser(MEI_TESTFILES[4]) + parser = MeiParser(MEI_TESTFILES[0]) main_partgroup_el = parser.document.find(parser._ns_name("staffGrp", all=True)) part_list = parser._handle_main_staff_group(main_partgroup_el) self.assertTrue(len(part_list) == 1) self.assertTrue(isinstance(part_list[0], score.PartGroup)) def test_handle_layer1(self): - parser = MeiParser(MEI_TESTFILES[5]) + parser = MeiParser(MEI_TESTFILES[1]) layer_el = [ e for e in parser.document.findall(parser._ns_name("layer", all=True)) @@ -83,7 +83,7 @@ def test_handle_layer1(self): self.assertTrue(len(part.note_array()) == 3) def test_handle_layer2(self): - parser = MeiParser(MEI_TESTFILES[5]) + parser = MeiParser(MEI_TESTFILES[1]) layer_el = [ e for e in parser.document.findall(parser._ns_name("layer", all=True)) @@ -94,7 +94,7 @@ def test_handle_layer2(self): self.assertTrue(len(part.note_array()) == 3) def test_handle_layer_tuplets(self): - parser = MeiParser(MEI_TESTFILES[6]) + parser = MeiParser(MEI_TESTFILES[2]) layer_el = [ e for e in parser.document.findall(parser._ns_name("layer", all=True)) @@ -105,13 +105,13 @@ def test_handle_layer_tuplets(self): self.assertTrue(len(part.note_array()) == 10) def test_ties1(self): - scr = load_mei(MEI_TESTFILES[7]) + scr = load_mei(MEI_TESTFILES[3]) part_list = scr.parts note_array = list(score.iter_parts(part_list))[0].note_array() self.assertTrue(len(note_array) == 4) def test_time_signatures(self): - scr = load_mei(MEI_TESTFILES[8]) + scr = load_mei(MEI_TESTFILES[4]) part_list = scr.parts part0 = list(score.iter_parts(part_list))[0] time_signatures = list(part0.iter_all(score.TimeSignature)) @@ -121,7 +121,7 @@ def test_time_signatures(self): self.assertTrue(time_signatures[2].start.t == 12.5 * 16) def test_clef(self): - part_list = load_mei(MEI_TESTFILES[9]).parts + part_list = load_mei(MEI_TESTFILES[5]).parts # test on part 2 part2 = list(score.iter_parts(part_list))[2] clefs2 = list(part2.iter_all(score.Clef)) @@ -148,7 +148,7 @@ def test_clef(self): self.assertTrue(clefs3[1].octave_change == -1) def test_key_signature1(self): - part_list = load_mei(MEI_TESTFILES[9]).parts + part_list = load_mei(MEI_TESTFILES[5]).parts for part in score.iter_parts(part_list): kss = list(part.iter_all(score.KeySignature)) self.assertTrue(len(kss) == 2) @@ -156,14 +156,14 @@ def test_key_signature1(self): self.assertTrue(kss[1].fifths == 4) def test_key_signature2(self): - part_list = load_mei(MEI_TESTFILES[10]).parts + part_list = load_mei(MEI_TESTFILES[6]).parts for part in score.iter_parts(part_list): kss = list(part.iter_all(score.KeySignature)) self.assertTrue(len(kss) == 1) self.assertTrue(kss[0].fifths == -1) def test_grace_note(self): - part_list = load_mei(MEI_TESTFILES[10]).parts + part_list = load_mei(MEI_TESTFILES[6]).parts part = list(score.iter_parts(part_list))[0] grace_notes = list(part.iter_all(score.GraceNote)) self.assertTrue(len(part.note_array()) == 7) @@ -172,42 +172,42 @@ def test_grace_note(self): self.assertTrue(grace_notes[1].grace_type == "appoggiatura") def test_meter_in_scoredef(self): - part_list = load_mei(MEI_TESTFILES[11]).parts + part_list = load_mei(MEI_TESTFILES[7]).parts self.assertTrue(True) def test_infer_ppq(self): - parser = MeiParser(MEI_TESTFILES[12]) + parser = MeiParser(MEI_TESTFILES[8]) inferred_ppq = parser._find_ppq() self.assertTrue(inferred_ppq == 15) def test_no_ppq(self): # compare the same piece with and without ppq annotations - parts_ppq = load_mei(MEI_TESTFILES[6]).parts + parts_ppq = load_mei(MEI_TESTFILES[2]).parts part_ppq = list(score.iter_parts(parts_ppq))[0] note_array_ppq = part_ppq.note_array() - parts_no_ppq = load_mei(MEI_TESTFILES[12]).parts + parts_no_ppq = load_mei(MEI_TESTFILES[8]).parts part_no_ppq = list(score.iter_parts(parts_no_ppq))[0] note_array_no_ppq = part_no_ppq.note_array() self.assertTrue(np.array_equal(note_array_ppq, note_array_no_ppq)) def test_part_duration(self): - parts_no_ppq = load_mei(MEI_TESTFILES[14]).parts + parts_no_ppq = load_mei(MEI_TESTFILES[10]).parts part_no_ppq = list(score.iter_parts(parts_no_ppq))[0] note_array_no_ppq = part_no_ppq.note_array() self.assertTrue(part_no_ppq._quarter_durations[0] == 4) self.assertTrue(sorted(part_no_ppq._points)[-1].t == 12) def test_part_duration2(self): - parts_no_ppq = load_mei(MEI_TESTFILES[15]).parts + parts_no_ppq = load_mei(MEI_TESTFILES[11]).parts part_no_ppq = list(score.iter_parts(parts_no_ppq))[0] note_array_no_ppq = part_no_ppq.note_array() self.assertTrue(part_no_ppq._quarter_durations[0] == 8) self.assertTrue(sorted(part_no_ppq._points)[-1].t == 22) def test_barline(self): - parts = load_mei(MEI_TESTFILES[16]).parts + parts = load_mei(MEI_TESTFILES[12]).parts part = list(score.iter_parts(parts))[0] barlines = list(part.iter_all(score.Barline)) expected_barlines_times = [0, 8, 8, 16, 20, 24, 28] @@ -224,7 +224,7 @@ def test_barline(self): self.assertTrue([bl.style for bl in barlines] == expected_barlines_style) def test_repetition1(self): - parts = load_mei(MEI_TESTFILES[16]).parts + parts = load_mei(MEI_TESTFILES[12]).parts part = list(score.iter_parts(parts))[0] repetitions = list(part.iter_all(score.Repeat)) expected_repeat_starts = [0, 8] @@ -233,7 +233,7 @@ def test_repetition1(self): self.assertTrue([rp.end.t for rp in repetitions] == expected_repeat_ends) def test_repetition2(self): - parts = load_mei(MEI_TESTFILES[17]).parts + parts = load_mei(MEI_TESTFILES[13]).parts part = list(score.iter_parts(parts))[0] fine_els = list(part.iter_all(score.Fine)) self.assertTrue(len(fine_els) == 1) @@ -253,26 +253,27 @@ def test_parse_mei_example(self): def test_parse_mei(self): # check if all test files load correctly - for mei in MEI_TESTFILES[4:]: + for mei in MEI_TESTFILES: + print("loading {}".format(mei)) part_list = load_mei(mei).parts self.assertTrue(True) def test_voice(self): - parts = load_mei(MEI_TESTFILES[19]) + parts = load_mei(MEI_TESTFILES[15]) merged_part = score.merge_parts(parts, reassign="voice") voices = merged_part.note_array()["voice"] expected_voices = [5, 4, 3, 2, 1, 1] self.assertTrue(np.array_equal(voices, expected_voices)) def test_staff(self): - parts = load_mei(MEI_TESTFILES[19]) + parts = load_mei(MEI_TESTFILES[15]) merged_part = score.merge_parts(parts, reassign="staff") staves = merged_part.note_array(include_staff=True)["staff"] expected_staves = [4, 3, 2, 1, 1, 1] self.assertTrue(np.array_equal(staves, expected_staves)) def test_nopart(self): - parts = load_mei(MEI_TESTFILES[20]) + parts = load_mei(MEI_TESTFILES[16]) last_measure_duration = [ list(p.iter_all(score.Measure))[-1].end.t - list(p.iter_all(score.Measure))[-1].start.t @@ -280,6 +281,9 @@ def test_nopart(self): ] self.assertTrue(all([d == 4096 for d in last_measure_duration])) + def test_tuplet_div(self): + score = load_mei(MEI_TESTFILES[17]) + self.assertTrue(np.array_equal(score.note_array()["duration_div"],[3,3,3,3,3,3,3,3,24])) if __name__ == "__main__": unittest.main() From 0f37837632df7ffac29ec665fe78047a6750746e Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 2 Aug 2023 15:55:36 +0200 Subject: [PATCH 13/80] removed problematic test that was using musescore 4 --- tests/__init__.py | 2 +- tests/test_musescore.py | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 2a3dad52..ce7bf09c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -187,7 +187,7 @@ MUSESCORE_TESTFILES = [ os.path.join(DATA_PATH, "musescore", fn) - for fn in ["mozart_k265_var1.mscz", "160.03_Pastorale.mscx"] + for fn in ["160.03_Pastorale.mscx"] ] KERN_TIES = [os.path.join(KERN_PATH, fn) for fn in ["tie_mismatch.krn"]] diff --git a/tests/test_musescore.py b/tests/test_musescore.py index e99728b4..9fcc2273 100644 --- a/tests/test_musescore.py +++ b/tests/test_musescore.py @@ -21,19 +21,10 @@ class TestImportMusescore(unittest.TestCase): - def test_number_of_parts1(self): - # dirty trick, since we can install Musescore only on linux environment in github actions - if platform.system() == "Linux": - score = load_via_musescore(MUSESCORE_TESTFILES[0]) - self.assertTrue(len(score.parts) == 1) - self.assertTrue(len(score.note_array()) == 218) - else: - self.skipTest("MuseScore test can't run on non-linux environment in github actions") - def test_epfl_scores(self): # dirty trick, since we can install Musescore only on linux environment in github actions if platform.system() == "Linux": - score = load_via_musescore(MUSESCORE_TESTFILES[1]) + score = load_via_musescore(MUSESCORE_TESTFILES[0]) self.assertTrue(len(score.parts) == 1) # try the generic loading function score = load_score(MUSESCORE_TESTFILES[1]) From 302adcf3e65fba4e26dcd49c18926c27e2ef4582 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 2 Aug 2023 15:55:43 +0200 Subject: [PATCH 14/80] removed file --- tests/data/musescore/mozart_k265_var1.mscz | Bin 33081 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tests/data/musescore/mozart_k265_var1.mscz diff --git a/tests/data/musescore/mozart_k265_var1.mscz b/tests/data/musescore/mozart_k265_var1.mscz deleted file mode 100644 index 54a96f328416853774c727c61e7c7e410927a301..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33081 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 From 31aa38c36978a5e2b31c1c3bfd95e984c2e3b80e Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 2 Aug 2023 16:30:27 +0200 Subject: [PATCH 15/80] Measures have only start and no end, like in import musicxml. Corrected test accordingly --- partitura/io/importmei.py | 18 +++++++++--------- tests/test_mei.py | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 615df845..520f93fd 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -1039,15 +1039,15 @@ def _handle_section(self, section_el, parts, position: int): warnings.warn( f"Warning : parts have measures of different duration in measure {element.attrib[self._ns_name('id',XML_NAMESPACE)]}" ) - # enlarge measures to the max - for part in parts: - last_measure = list(part.iter_all(pt.score.Measure))[-1] - if last_measure.end.t != max_position: - part.add( - pt.score.Measure(number=last_measure.number), - max_position - ) - part.remove(last_measure) + # # enlarge measures to the max + # for part in parts: + # last_measure = list(part.iter_all(pt.score.Measure))[-1] + # if last_measure.end.t != max_position: + # part.add( + # pt.score.Measure(number=last_measure.number), + # max_position + # ) + # part.remove(last_measure) position = max_position # handle right barline symbol self._handle_barline_symbols(element, position, "right") diff --git a/tests/test_mei.py b/tests/test_mei.py index 1564504a..d580b551 100644 --- a/tests/test_mei.py +++ b/tests/test_mei.py @@ -273,11 +273,11 @@ def test_staff(self): self.assertTrue(np.array_equal(staves, expected_staves)) def test_nopart(self): - parts = load_mei(MEI_TESTFILES[16]) + my_score = load_mei(MEI_TESTFILES[16]) last_measure_duration = [ - list(p.iter_all(score.Measure))[-1].end.t + list(p.iter_all(score.Barline))[-1].start.t - list(p.iter_all(score.Measure))[-1].start.t - for p in parts + for p in my_score.parts ] self.assertTrue(all([d == 4096 for d in last_measure_duration])) From 920856d2d1f353f9fb92b2b36d8622dd7d7719e6 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 2 Aug 2023 16:37:22 +0200 Subject: [PATCH 16/80] add force flags for musescore --- partitura/io/musescore.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 223d2e90..67dd5c1c 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -119,19 +119,22 @@ def load_via_musescore( One or more part or partgroup objects """ - # open the file as text and check if the first symbol is "<" to avoid - # further processing in case of non-XML files - with open(filename, "r") as f: - if f.read(1) != "<": - raise FileImportException( - "File {} is not a valid XML file.".format(filename) - ) + if filename.endswith(".mscz"): + pass + else: + # open the file as text and check if the first symbol is "<" to avoid + # further processing in case of non-XML files + with open(filename, "r") as f: + if f.read(1) != "<": + raise FileImportException( + "File {} is not a valid XML file.".format(filename) + ) mscore_exec = find_musescore() xml_fh = os.path.splitext(os.path.basename(filename))[0] + ".musicxml" - cmd = [mscore_exec, "-o", xml_fh, filename] + cmd = [mscore_exec, "-o", xml_fh, filename, "-f"] try: ps = subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) @@ -210,6 +213,7 @@ def render_musescore( "-o", os.fspath(img_fh), os.fspath(xml_fh), + "-f" ] try: ps = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) From c25cd52058165ea0396a1cee969993f3a27d3fbc Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 2 Aug 2023 16:38:23 +0200 Subject: [PATCH 17/80] updated workflow to install musescore3 --- .github/workflows/partitura_unittests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index b4feab90..44b5ed14 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -28,7 +28,7 @@ jobs: - if: matrix.platform == 'ubuntu-latest' name: Install Musescore run: | - sudo apt-get install musescore + sudo apt-get install musescore3 - name: Install Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 From 4e883e73445e71ea11073d8241d0504ff10cdd50 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 2 Aug 2023 16:54:04 +0200 Subject: [PATCH 18/80] Removed Musescore Tests, because they can't run on github --- .github/workflows/partitura_unittests.yml | 4 ---- tests/test_musescore.py | 19 ++++++++----------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/workflows/partitura_unittests.yml b/.github/workflows/partitura_unittests.yml index 44b5ed14..a0c2dd08 100644 --- a/.github/workflows/partitura_unittests.yml +++ b/.github/workflows/partitura_unittests.yml @@ -25,10 +25,6 @@ jobs: python -m pip install --upgrade pip pip install -r requirements.txt pip install . - - if: matrix.platform == 'ubuntu-latest' - name: Install Musescore - run: | - sudo apt-get install musescore3 - name: Install Optional dependencies run: | pip install music21==8.3.0 Pillow==9.5.0 musescore==0.0.1 diff --git a/tests/test_musescore.py b/tests/test_musescore.py index 9fcc2273..93529ff6 100644 --- a/tests/test_musescore.py +++ b/tests/test_musescore.py @@ -19,15 +19,12 @@ import numpy as np from pathlib import Path +# This tests are commented, because it is not possible to run MuseScore on a Github -class TestImportMusescore(unittest.TestCase): - def test_epfl_scores(self): - # dirty trick, since we can install Musescore only on linux environment in github actions - if platform.system() == "Linux": - score = load_via_musescore(MUSESCORE_TESTFILES[0]) - self.assertTrue(len(score.parts) == 1) - # try the generic loading function - score = load_score(MUSESCORE_TESTFILES[1]) - self.assertTrue(len(score.parts) == 1) - else: - self.skipTest("MuseScore test can't run on non-linux environment in github actions") \ No newline at end of file +# class TestImportMusescore(unittest.TestCase): +# def test_epfl_scores(self): +# score = load_via_musescore(MUSESCORE_TESTFILES[0]) +# self.assertTrue(len(score.parts) == 1) +# # try the generic loading function +# score = load_score(MUSESCORE_TESTFILES[0]) +# self.assertTrue(len(score.parts) == 1) \ No newline at end of file From 1ce4a02430cf29786af0de21c171bd5b813da1d4 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Thu, 3 Aug 2023 12:06:10 +0200 Subject: [PATCH 19/80] added unfolding part minimal: Also changed unfolding_part_minimal and unfolding part_maximal to work with scores. --- partitura/score.py | 65 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/partitura/score.py b/partitura/score.py index 42554381..76246ba0 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -9,7 +9,7 @@ are registered in terms of their start and end times. """ -from copy import copy +from copy import copy, deepcopy from collections import defaultdict from collections.abc import Iterable from numbers import Number @@ -4588,15 +4588,58 @@ def iter_unfolded_parts(part, update_ids=True): # UPDATED VERSION -def unfold_part_maximal(part, update_ids=True, ignore_leaps=True): - """Return the "maximally" unfolded part, that is, a copy of the +def unfold_part_maximal(score: ScoreLike, update_ids=True, ignore_leaps=True): + """Return the "maximally" unfolded part/score, that is, a copy of the part where all segments marked with repeat signs are included twice. Parameters ---------- - part : :class:`Part` - The Part to unfold. + score : :class:`Part` + The Part/Score to unfold. + update_ids : bool (optional) + Update note ids to reflect the repetitions. Note IDs will have + a '-', e.g., 'n132-1' and 'n132-2' + represent the first and second repetition of 'n132' in the + input `part`. Defaults to False. + ignore_leaps : bool (optional) + If ignored, repetitions after a leap are unfolded fully. + A leap is a used dal segno, da capo, or al coda marking. + Defaults to True. + + Returns + ------- + unfolded_part : :class:`Part` + The unfolded Part + + """ + if isinstance(score, Score): + new_score = deepcopy(score) + new_partlist = list() + for score in new_score.parts: + unfolded_part = unfold_part_maximal(score, update_ids=update_ids, ignore_leaps=ignore_leaps) + new_partlist.append(unfolded_part) + new_score.parts = new_partlist + return new_score + + paths = get_paths( + score, no_repeats=False, all_repeats=True, ignore_leap_info=ignore_leaps + ) + + unfolded_part = new_part_from_path(paths[0], score, update_ids=update_ids) + return unfolded_part + + +def unfold_part_minimal(score: ScoreLike, update_ids=True, ignore_leaps=True): + """Return the "minimally" unfolded score/part, that is, a copy of the + part where all segments marked with repeat or volta signs are not included. + For voltas only the last volta segment is included. + + + Parameters + ---------- + score: ScoreLike + The score/part to unfold. update_ids : bool (optional) Update note ids to reflect the repetitions. Note IDs will have a '-', e.g., 'n132-1' and 'n132-2' @@ -4613,12 +4656,20 @@ def unfold_part_maximal(part, update_ids=True, ignore_leaps=True): The unfolded Part """ + if isinstance(score, Score): + new_score = deepcopy(score) + new_partlist = list() + for part in new_score.parts: + unfolded_part = unfold_part_minimal(part, update_ids=update_ids, ignore_leaps=ignore_leaps) + new_partlist.append(unfolded_part) + new_score.parts = new_partlist + return new_score paths = get_paths( - part, no_repeats=False, all_repeats=True, ignore_leap_info=ignore_leaps + score, no_repeats=True, all_repeats=True, ignore_leap_info=ignore_leaps ) - unfolded_part = new_part_from_path(paths[0], part, update_ids=update_ids) + unfolded_part = new_part_from_path(paths[0], score, update_ids=update_ids) return unfolded_part From 4be4bbe3424f5bdc08f8f6d2c8f48032bf844eec Mon Sep 17 00:00:00 2001 From: huispaty Date: Thu, 3 Aug 2023 14:58:15 +0200 Subject: [PATCH 20/80] new score attributes and score prop line for match export, new Score attributes, note id fix in load_performance_midi, optional track merging param in save_performance_midi --- partitura/directions.py | 3 + partitura/io/exportmatch.py | 50 ++- partitura/io/exportmidi.py | 14 +- partitura/io/importmidi.py | 22 +- partitura/io/importmusicxml.py | 45 ++- partitura/io/matchfile_utils.py | 41 ++ partitura/io/matchlines_v1.py | 9 +- partitura/score.py | 47 ++- tests/data/match/mozart_k265_var1.match | 513 ++++++++++-------------- tests/test_match_import.py | 3 +- tests/test_tonal_tension.py | 6 +- 11 files changed, 435 insertions(+), 318 deletions(-) diff --git a/partitura/directions.py b/partitura/directions.py index 80043d13..b52024f6 100644 --- a/partitura/directions.py +++ b/partitura/directions.py @@ -151,6 +151,8 @@ def unabbreviate(s): "adagio", "agitato", "andante", + "andante cantabile", + "andante amoroso", "andantino", "animato", "appassionato", @@ -193,6 +195,7 @@ def unabbreviate(s): "tranquilamente", "tranquilo", "recitativo", + "allegro moderato", r"/(vivo|vivacissimamente|vivace)/", r"/(allegro|allegretto)/", r"/(espressivo|espress\.?)/", diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index a23eee23..c75cb82e 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -8,6 +8,7 @@ * The methods only export matchfiles version 1.0.0. """ import numpy as np +import pandas as pd from typing import List, Optional, Iterable @@ -37,6 +38,7 @@ FractionalSymbolicDuration, MatchKeySignature, MatchTimeSignature, + MatchTempoIndication, Version, ) @@ -71,6 +73,8 @@ def matchfile_from_alignment( score_filename: Optional[PathLike] = None, performance_filename: Optional[PathLike] = None, assume_part_unfolded: bool = False, + tempo_indication: Optional[str] = None, + diff_score_version_notes: Optional[list] = None, version: Version = LATEST_VERSION, debug: bool = False, ) -> MatchFile: @@ -106,6 +110,10 @@ def matchfile_from_alignment( repetitions in the alignment. If False, the part will be automatically unfolded to have maximal coverage of the notes in the alignment. See `partitura.score.unfold_part_alignment`. + tempo_indication : str or None + The tempo direction indicated in the beginning of the score + diff_score_version_notes : list or None + A list of score notes that reflect a special score version (e.g., original edition/Erstdruck, Editors note etc.) version: Version Version of the match file. For now only 1.0.0 is supported. Returns @@ -180,7 +188,7 @@ def matchfile_from_alignment( alignment=alignment, remove_ornaments=True, ) - + measures = np.array(list(spart.iter_all(score.Measure))) measure_starts_divs = np.array([m.start.t for m in measures]) measure_starts_beats = beat_map(measure_starts_divs) @@ -276,7 +284,6 @@ def matchfile_from_alignment( # Get all notes in the measure snotes = spart.iter_all(score.Note, m.start, m.end, include_subclasses=True) # Beginning of each measure - for snote in snotes: onset_divs, offset_divs = snote.start.t, snote.start.t + snote.duration_tied duration_divs = offset_divs - onset_divs @@ -323,6 +330,12 @@ def matchfile_from_alignment( if fermata is not None: score_attributes_list.append("fermata") + + if isinstance(snote, score.GraceNote): + score_attributes_list.append("grace") + + if diff_score_version_notes is not None and snote.id in diff_score_version_notes: + score_attributes_list.append("diff_score_version") score_info[snote.id] = MatchSnote( version=version, @@ -346,6 +359,22 @@ def matchfile_from_alignment( ) snote_sort_info[snote.id] = (onset_beats, snote.doc_order) + # # NOTE time position is hardcoded, not pretty... Assumes there is only one tempo indication at the beginning of the score + if tempo_indication is not None: + score_tempo_direction_header = make_scoreprop( + version=version, + attribute="tempoIndication", + value=MatchTempoIndication( + tempo_indication, + is_list=False, + ), + measure=measure_starts[0][0], + beat=1, + offset=0, + time_in_beats=measure_starts[0][2], + ) + scoreprop_lines["tempo_indication"].append(score_tempo_direction_header) + perf_info = dict() pnote_sort_info = dict() for pnote in ppart.notes: @@ -372,6 +401,15 @@ def matchfile_from_alignment( sort_stime = [] note_lines = [] + + # Filter out voice overlap notes using a pd.Dataframe structure + spart_df = pd.DataFrame(spart.note_array()) + voice_overlap_candidates = spart_df.groupby(['onset_beat', 'pitch']) + voice_overlap_note_ids = [] + for _, group in voice_overlap_candidates: + if len(group) > 1: + voice_overlap_note_ids.extend(np.array(group.values.tolist())[:,-1].tolist()) + for al_note in alignment: label = al_note["label"] @@ -384,6 +422,8 @@ def matchfile_from_alignment( elif label == "deletion": snote = score_info[al_note["score_id"]] + if al_note["score_id"] in voice_overlap_note_ids: + snote.ScoreAttributesList.append('voice_overlap') deletion_line = MatchSnoteDeletion(version=version, snote=snote) note_lines.append(deletion_line) sort_stime.append(snote_sort_info[al_note["score_id"]]) @@ -407,10 +447,11 @@ def matchfile_from_alignment( note_lines.append(ornament_line) sort_stime.append(pnote_sort_info[al_note["performance_id"]]) - + # sort notes by score onset (performed insertions are sorted # according to the interpolation map sort_stime = np.array(sort_stime) + # print(sort_stime) sort_stime_idx = np.lexsort((sort_stime[:, 1], sort_stime[:, 0])) note_lines = np.array(note_lines)[sort_stime_idx] @@ -441,6 +482,7 @@ def matchfile_from_alignment( "clock_rate", "key_signatures", "time_signatures", + "tempo_indication", ] all_match_lines = [] for h in header_order: @@ -537,7 +579,7 @@ def save_match( else: raise ValueError( "`performance_data` should be a `Performance`, a `PerformedPart`, or a " - f"list of `PerformedPart` objects, but is {type(score_data)}" + f"list of `PerformedPart` objects, but is {type(performance_data)}" ) # Get matchfile diff --git a/partitura/io/exportmidi.py b/partitura/io/exportmidi.py index 61960d99..2b52e386 100644 --- a/partitura/io/exportmidi.py +++ b/partitura/io/exportmidi.py @@ -8,7 +8,7 @@ from collections import defaultdict, OrderedDict from typing import Optional, Iterable -from mido import MidiFile, MidiTrack, Message, MetaMessage +from mido import MidiFile, MidiTrack, Message, MetaMessage, merge_tracks import partitura.score as score from partitura.score import Score, Part, PartGroup, ScoreLike @@ -87,6 +87,7 @@ def save_performance_midi( mpq: int = 500000, ppq: int = 480, default_velocity: int = 64, + merge_tracks_save: Optional[bool] = False, ) -> Optional[MidiFile]: """Save a :class:`~partitura.performance.PerformedPart` or a :class:`~partitura.performance.Performance` as a MIDI file @@ -107,6 +108,8 @@ def save_performance_midi( default_velocity : int, optional A default velocity value (between 0 and 127) to be used for notes without a specified velocity. Defaults to 64. + merge_tracks_save : bool, optional + Determines whether midi tracks are merged when exporting to a midi file. Defaults to False. Returns ------- @@ -134,7 +137,6 @@ def save_performance_midi( ) track_events = defaultdict(lambda: defaultdict(list)) - for performed_part in performed_parts: for c in performed_part.controls: track = c.get("track", 0) @@ -200,7 +202,9 @@ def save_performance_midi( track_events[tr][min(timepoints)].append( Message("program_change", program=0, channel=ch) ) - + # TODOph fix diff i/o notes + print('==== exported midi notes:', len(performed_part.notes)) + midi_type = 0 if len(track_events) == 1 else 1 mf = MidiFile(type=midi_type, ticks_per_beat=ppq) @@ -217,6 +221,10 @@ def save_performance_midi( track.append(msg.copy(time=t_delta)) t_delta = 0 t = t_msg + + if merge_tracks_save and len(mf.tracks) > 1: + mf.tracks = [merge_tracks(mf.tracks)] + if out is not None: if hasattr(out, "write"): mf.save(file=out) diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index ecfe2be3..72082e0d 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -176,18 +176,34 @@ def load_performance_midi( note = note_hash(msg.channel, msg.note) # start note if it's a 'note on' event with velocity > 0 + # key_exists = False if note_on and msg.velocity > 0: + # note_on_ctr += 1 + # if note in sounding_notes.keys(): + # key_exists = True + # print('key_exists') + # print(f'\t{note} {sounding_notes[note]} - existing key') + # print('\tmsg number:', msg_i, msg, 'incoming msg') + # save the onset time and velocity sounding_notes[note] = (t, ttick, msg.velocity) + + # if key_exists: + # print(f'\t{note} {sounding_notes[note]} - overwritten key') + # key_exists = False # end note if it's a 'note off' event or 'note on' with velocity 0 elif note_off or (note_on and msg.velocity == 0): if note not in sounding_notes: - warnings.warn("ignoring MIDI message %s" % msg) + # print('ignoring:') + # print('\tmsg number:', msg_i, msg) + # print(f'\t{note} {(t, ttick, msg.velocity)}') + # print((f"\tMIDI message {msg}")) + warnings.warn(f"ignoring MIDI message {msg}, {note}") + print() continue # append the note to the list associated with the channel - notes.append( dict( # id=f"n{len(notes)}", @@ -218,7 +234,7 @@ def load_performance_midi( # add note id to every note for k, note in enumerate(notes): - note["id"] = f"n{k}" + note["id"] = f"n{k+1}" if len(notes) > 0 or len(controls) > 0 or len(programs) > 0: pp = performance.PerformedPart( diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index c7fc2bb8..6540a004 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -60,6 +60,22 @@ "sustain_pedal": score.SustainPedalDirection, } +TEMPO_DIRECTIONS = { + "Adagio": score.ConstantTempoDirection, + "Andante": score.ConstantTempoDirection, + "Andante amoroso": score.ConstantTempoDirection, + "Andante cantabile": score.ConstantTempoDirection, + "Andante grazioso": score.ConstantTempoDirection, + "Menuetto": score.ConstantTempoDirection, + "Allegretto grazioso": score.ConstantTempoDirection, + "Allegro moderato": score.ConstantTempoDirection, + "Allegro assai": score.ConstantTempoDirection, + "Allegro": score.ConstantTempoDirection, + "Allegretto": score.ConstantTempoDirection, + "Molto allegro": score.ConstantTempoDirection, + "Presto": score.ConstantTempoDirection, +} + OCTAVE_SHIFTS = {8: 1, 15: 2, 22: 3} @@ -114,7 +130,6 @@ def _parse_partlist(partlist): structure = [] current_group = None part_dict = {} - for e in partlist: if e.tag == "part-group": if e.get("type") == "start": @@ -146,7 +161,6 @@ def _parse_partlist(partlist): part.part_abbreviation = next( iter(e.xpath("part-abbreviation/text()")), None ) - part_dict[part_id] = part if current_group is None: @@ -239,11 +253,15 @@ def load_musicxml( composer = None scid = None + work_title = None + work_number = None + movement_title = None + movement_number = None title = None subtitle = None lyricist = None copyright = None - + # The work tag is preferred for the title of the score, otherwise # this method will search in the credit tags work_info_el = document.find("work") @@ -254,8 +272,20 @@ def load_musicxml( tag="work-title", as_type=str, ) + scidn = get_value_from_tag( + e=work_info_el, + tag="work-number", + as_type=str, + ) + work_title = scid + work_number = scidn - title = scid + movement_title_el = document.find('.//movement-title') + movement_number_el = document.find('.//movement-number') + if movement_title_el is not None: + movement_title = movement_title_el.text + if movement_number_el is not None: + movement_number = movement_number_el.text score_identification_el = document.find("identification") @@ -289,6 +319,10 @@ def load_musicxml( scr = score.Score( id=scid, partlist=partlist, + work_number=work_number, + work_title=work_title, + movement_number=movement_number, + movement_title=movement_title, title=title, subtitle=subtitle, composer=composer, @@ -879,6 +913,7 @@ def _handle_direction(e, position, part, ongoing): warnings.warn("Did not find a wedge start element for wedge stop!") elif dt.tag == "dashes": + # start/stop/continue dashes_type = get_value_from_attribute(dt, "type", str) number = get_value_from_attribute(dt, "number", int) or 1 @@ -1137,7 +1172,7 @@ def _handle_sound(e, position, part): if "tempo" in e.attrib: tempo = score.Tempo(int(e.attrib["tempo"]), "q") # part.add_starting_object(position, tempo) - _add_tempo_if_unique(position, part, tempo) + (position, part, tempo) def _handle_note(e, position, part, ongoing, prev_note, doc_order): diff --git a/partitura/io/matchfile_utils.py b/partitura/io/matchfile_utils.py index 4ad49b27..3f07cc2d 100644 --- a/partitura/io/matchfile_utils.py +++ b/partitura/io/matchfile_utils.py @@ -871,6 +871,47 @@ def format_time_signature_list(value: MatchTimeSignature) -> str: return str(value) +class MatchTempoIndication(MatchParameter): + def __init__( + self, + value: str, + is_list: bool = False, + ): + super().__init__() + self.value = self.from_string(value)[0] + self.is_list = is_list + + def __str__(self): + return self.value + + @classmethod + + def from_string(cls, string: str) -> MatchTempoIndication: + + # Note particularities of the BpM dataset.... + if string is not None: + if 'Rond' in string: + content = string.split(' ') + content = [content[-1]] + elif 'Alla' in string: # for kv331_3 + content = ['Allegretto'] + elif 'Menuetto' in string: + content = ['Menuetto'] + else: + content = interpret_as_list(string) + + return content + + +def interpret_as_tempo_indication(value: str) -> MatchTempoIndication: + tempo_indication = MatchTempoIndication.from_string(value) + return tempo_indication + +def format_tempo_indication(value: MatchTempoIndication) -> str: + value.is_list = False + return str(value) + + ## Miscellaneous utils diff --git a/partitura/io/matchlines_v1.py b/partitura/io/matchlines_v1.py index 0900e969..1b1b3fbd 100644 --- a/partitura/io/matchlines_v1.py +++ b/partitura/io/matchlines_v1.py @@ -58,6 +58,9 @@ format_key_signature_v1_0_0, to_snake_case, get_kwargs_from_matchline, + MatchTempoIndication, + interpret_as_tempo_indication, + format_tempo_indication, ) # Define current version of the match file format @@ -224,7 +227,6 @@ def from_instance( format_fun=format_fun, ) - SCOREPROP_LINE = { Version(1, 0, 0): { "timeSignature": ( @@ -237,6 +239,11 @@ def from_instance( format_key_signature_v1_0_0, MatchKeySignature, ), + "tempoIndication": ( + interpret_as_tempo_indication, + format_tempo_indication, + MatchTempoIndication, + ), "beatSubDivision": (interpret_as_list_int, format_list, list), "directions": (interpret_as_list, format_list, list), } diff --git a/partitura/score.py b/partitura/score.py index 42554381..fef5163b 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -615,6 +615,18 @@ def dynamics(self): """ return [e for e in self.iter_all(LoudnessDirection, include_subclasses=True)] + @property + def tempo_directions(self): + """Return a list of all tempo direction in the part + + Returns + ------- + list + List of TempoDirection objects + + """ + return [e for e in self.iter_all(TempoDirection, include_subclasses=True)] + @property def articulations(self): """Return a list of all Articulation markings in the part @@ -2794,7 +2806,7 @@ def __str__(self): else: return f'{super().__str__()} "{self.text}"' - + class LoudnessDirection(Direction): pass @@ -2830,7 +2842,6 @@ class ConstantLoudnessDirection(ConstantDirection, LoudnessDirection): class ConstantTempoDirection(ConstantDirection, TempoDirection): pass - class ConstantArticulationDirection(ConstantDirection, ArticulationDirection): pass @@ -2990,8 +3001,16 @@ class Score(object): the identifier should not start with a number. partlist : `Part`, `PartGroup` or list of `Part` or `PartGroup` instances. List of `Part` or `PartGroup` objects. - title: str, optional - Title of the score. + work_title: str, optional + Work title of the score, if applicable. + work_number: str, optional + Work number of the score, if applicable. + movement_title: str, optional + Movement title of the score, if applicable. + movement_number: str, optional + Movement number of the score, if applicable. + title : str, optional + Title of the score, from tag subtitle: str, optional Subtitle of the score. composer: str, optional @@ -3010,7 +3029,13 @@ class Score(object): part_structure: list of `Part` or `PartGrop` List of all `Part` or `PartGroup` objects that specify the structure of the score. - title: str + work_title: str + See parameters. + work_number: str + See parameters. + movement_title: str + See parameters. + movement_number: str See parameters. subtitle: str See parameters. @@ -3024,6 +3049,10 @@ class Score(object): """ id: Optional[str] + work_title: Optional[str] + work_number: Optional[str] + movement_title: Optional[str] + movement_number: Optional[str] title: Optional[str] subtitle: Optional[str] composer: Optional[str] @@ -3036,6 +3065,10 @@ def __init__( self, partlist: Union[Part, PartGroup, Itertype[Union[Part, PartGroup]]], id: Optional[str] = None, + work_title: Optional[str] = None, + work_number: Optional[str] = None, + movement_title: Optional[str] = None, + movement_number: Optional[str] = None, title: Optional[str] = None, subtitle: Optional[str] = None, composer: Optional[str] = None, @@ -3045,6 +3078,10 @@ def __init__( self.id = id # Score Information (default from MuseScore/MusicXML) + self.work_title = work_title + self.work_number = work_number + self.movement_title = movement_title + self.movement_number = movement_number self.title = title self.subtitle = subtitle self.composer = composer diff --git a/tests/data/match/mozart_k265_var1.match b/tests/data/match/mozart_k265_var1.match index fbf69446..0fd2f9a5 100644 --- a/tests/data/match/mozart_k265_var1.match +++ b/tests/data/match/mozart_k265_var1.match @@ -3,300 +3,227 @@ info(piece,mozart_k265_var1). info(scoreFileName,mozart_k265_var1.musicxml). info(midiFileName,mozart_k265_var1.mid). info(composer,W. A. Mozart). -info(performer,Carlos Cancino-Chacón). +info(performer,A Pianist). info(midiClockUnits,480). info(midiClockRate,500000). scoreprop(keySignature,C,1:1,0,0.0000). scoreprop(timeSignature,2/4,1:1,0,0.0000). -snote(n1,[D,n],5,1:1,0,1/16,0.0000,0.2500,[v1,staff1])-note(n1,74,692,773,72,1,1). -snote(n9,[C,n],3,1:1,0,1/4,0.0000,1.0000,[v5,staff2])-note(n0,48,683,747,70,1,1). -snote(n2,[C,n],5,1:1,1/16,1/16,0.2500,0.5000,[v1,staff1])-note(n2,72,819,892,53,1,1). -snote(n3,[B,n],4,1:1,1/8,1/16,0.5000,0.7500,[v1,staff1])-note(n3,71,914,980,54,1,1). -snote(n4,[C,n],5,1:1,3/16,1/16,0.7500,1.0000,[v1,staff1])-note(n4,72,1009,1098,48,1,1). -snote(n5,[B,n],4,1:2,0,1/16,1.0000,1.2500,[v1,staff1])-note(n6,71,1128,1174,61,1,1). -snote(n10,[C,n],4,1:2,0,1/4,1.0000,2.0000,[v5,staff2])-note(n5,60,1122,1160,67,1,1). -snote(n6,[C,n],5,1:2,1/16,1/16,1.2500,1.5000,[v1,staff1])-note(n7,72,1233,1284,44,1,1). -snote(n7,[B,n],4,1:2,1/8,1/16,1.5000,1.7500,[v1,staff1])-note(n8,71,1316,1410,65,1,1). -snote(n8,[C,n],5,1:2,3/16,1/16,1.7500,2.0000,[v1,staff1])-note(n9,72,1420,1490,61,1,1). -snote(n11,[A,n],5,2:1,0,1/16,2.0000,2.2500,[v1,staff1])-note(n11,81,1556,1637,55,1,1). -snote(n19,[E,n],4,2:1,0,1/4,2.0000,3.0000,[v5,staff2])-note(n10,64,1541,1614,75,1,1). -snote(n12,[G,n],5,2:1,1/16,1/16,2.2500,2.5000,[v1,staff1])-note(n12,79,1683,1752,62,1,1). -snote(n13,[F,#],5,2:1,1/8,1/16,2.5000,2.7500,[v1,staff1])-note(n13,78,1785,1831,52,1,1). -snote(n14,[G,n],5,2:1,3/16,1/16,2.7500,3.0000,[v1,staff1])-note(n14,79,1893,1949,77,1,1). -snote(n15,[F,#],5,2:2,0,1/16,3.0000,3.2500,[v1,staff1])-note(n15,78,1984,2041,60,1,1). -snote(n20,[C,n],4,2:2,0,1/4,3.0000,4.0000,[v5,staff2])-note(n16,60,2002,2043,49,1,1). -snote(n16,[G,n],5,2:2,1/16,1/16,3.2500,3.5000,[v1,staff1])-note(n17,79,2100,2130,52,1,1). -snote(n17,[F,#],5,2:2,1/8,1/16,3.5000,3.7500,[v1,staff1])-note(n18,78,2180,2235,64,1,1). -snote(n18,[G,n],5,2:2,3/16,1/16,3.7500,4.0000,[v1,staff1])-note(n19,79,2275,2317,54,1,1). -snote(n21,[G,#],5,3:1,0,1/16,4.0000,4.2500,[v1,staff1])-note(n20,80,2392,2454,61,1,1). -snote(n29,[F,n],4,3:1,0,1/4,4.0000,5.0000,[v5,staff2])-note(n21,65,2416,2457,61,1,1). -snote(n22,[A,n],5,3:1,1/16,1/16,4.2500,4.5000,[v1,staff1])-note(n22,81,2495,2600,61,1,1). -snote(n23,[C,n],6,3:1,1/8,1/16,4.5000,4.7500,[v1,staff1])-note(n23,84,2611,2722,62,1,1). -snote(n24,[B,n],5,3:1,3/16,1/16,4.7500,5.0000,[v1,staff1])-note(n24,83,2722,2796,51,1,1). -snote(n25,[D,n],6,3:2,0,1/16,5.0000,5.2500,[v1,staff1])-note(n25,86,2807,2939,61,1,1). -snote(n30,[C,n],4,3:2,0,1/4,5.0000,6.0000,[v5,staff2])-note(n26,60,2836,2890,45,1,1). -snote(n26,[C,n],6,3:2,1/16,1/16,5.2500,5.5000,[v1,staff1])-note(n27,84,2939,3024,55,1,1). -snote(n27,[B,n],5,3:2,1/8,1/16,5.5000,5.7500,[v1,staff1])-note(n28,83,3033,3093,64,1,1). -snote(n28,[A,n],5,3:2,3/16,1/16,5.7500,6.0000,[v1,staff1])-note(n29,81,3138,3162,52,1,1). -snote(n31,[A,n],5,4:1,0,1/16,6.0000,6.2500,[v1,staff1])-note(n31,81,3280,3364,68,1,1). -snote(n39,[C,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n30,60,3275,3538,42,1,1). -snote(n40,[E,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n32,64,3291,3526,59,1,1). -snote(n32,[G,n],5,4:1,1/16,1/16,6.2500,6.5000,[v1,staff1])-note(n33,79,3413,3499,62,1,1). -snote(n33,[E,n],6,4:1,1/8,1/16,6.5000,6.7500,[v1,staff1])-note(n34,88,3531,3657,69,1,1). -snote(n34,[D,n],6,4:1,3/16,1/16,6.7500,7.0000,[v1,staff1])-note(n35,86,3639,3762,74,1,1). -snote(n35,[C,n],6,4:2,0,1/16,7.0000,7.2500,[v1,staff1])-note(n36,84,3758,3859,62,1,1). -snote(n36,[B,n],5,4:2,1/16,1/16,7.2500,7.5000,[v1,staff1])-note(n37,83,3845,3912,64,1,1). -snote(n37,[A,n],5,4:2,1/8,1/16,7.5000,7.7500,[v1,staff1])-note(n38,81,3933,3976,59,1,1). -snote(n38,[G,n],5,4:2,3/16,1/16,7.7500,8.0000,[v1,staff1])-note(n40,79,4058,4069,58,1,1). -snote(n42,[C,#],4,4:2,3/16,1/16,7.7500,8.0000,[v5,staff2])-note(n39,61,4050,4094,60,1,1). -snote(n43,[G,n],5,5:1,0,1/16,8.0000,8.2500,[v1,staff1])-note(n41,79,4187,4262,63,1,1). -snote(n51,[D,n],4,5:1,0,1/4,8.0000,9.0000,[v5,staff2])-note(n42,62,4203,4563,79,1,1). -snote(n44,[F,n],5,5:1,1/16,1/16,8.2500,8.5000,[v1,staff1])-note(n43,77,4289,4381,87,1,1). -snote(n45,[D,n],6,5:1,1/8,1/16,8.5000,8.7500,[v1,staff1])-note(n44,86,4410,4548,80,1,1). -snote(n46,[C,n],6,5:1,3/16,1/16,8.7500,9.0000,[v1,staff1])-note(n45,84,4531,4679,70,1,1). -snote(n47,[B,n],5,5:2,0,1/16,9.0000,9.2500,[v1,staff1])-note(n46,83,4637,4736,67,1,1). -snote(n48,[A,n],5,5:2,1/16,1/16,9.2500,9.5000,[v1,staff1])-note(n47,81,4730,4810,72,1,1). -snote(n49,[G,n],5,5:2,1/8,1/16,9.5000,9.7500,[v1,staff1])-note(n48,79,4838,4876,61,1,1). -snote(n50,[F,n],5,5:2,3/16,1/16,9.7500,10.0000,[v1,staff1])-note(n49,77,4939,4961,68,1,1). -snote(n53,[B,n],3,5:2,3/16,1/16,9.7500,10.0000,[v5,staff2])-note(n50,59,4944,4981,51,1,1). -snote(n54,[F,n],5,6:1,0,1/16,10.0000,10.2500,[v1,staff1])-note(n51,77,5062,5144,79,1,1). -snote(n62,[C,n],4,6:1,0,1/4,10.0000,11.0000,[v5,staff2])-note(n52,60,5099,5501,64,1,1). -snote(n55,[E,n],5,6:1,1/16,1/16,10.2500,10.5000,[v1,staff1])-note(n53,76,5176,5260,72,1,1). -snote(n56,[C,n],6,6:1,1/8,1/16,10.5000,10.7500,[v1,staff1])-note(n54,84,5303,5436,69,1,1). -snote(n57,[B,n],5,6:1,3/16,1/16,10.7500,11.0000,[v1,staff1])-note(n55,83,5407,5552,68,1,1). -snote(n58,[A,n],5,6:2,0,1/16,11.0000,11.2500,[v1,staff1])-note(n56,81,5509,5611,61,1,1). -snote(n59,[G,n],5,6:2,1/16,1/16,11.2500,11.5000,[v1,staff1])-note(n57,79,5596,5677,70,1,1). -snote(n60,[F,n],5,6:2,1/8,1/16,11.5000,11.7500,[v1,staff1])-note(n58,77,5684,5753,82,1,1). -snote(n61,[E,n],5,6:2,3/16,1/16,11.7500,12.0000,[v1,staff1])-note(n60,76,5809,5882,61,1,1). -snote(n64,[A,n],3,6:2,3/16,1/16,11.7500,12.0000,[v5,staff2])-note(n59,57,5793,5854,65,1,1). -snote(n65,[D,n],5,7:1,0,1/8,12.0000,12.5000,[v1,staff1])-note(n61,74,5922,6176,75,1,1). -snote(n69,[F,n],3,7:1,0,1/4,12.0000,13.0000,[v5,staff2])-note(n62,53,5978,6219,63,1,1). -snote(n66,[A,n],5,7:1,1/8,1/8,12.5000,13.0000,[v1,staff1])-note(n63,81,6197,6454,70,1,1). -snote(n67,[G,n],5,7:2,0,1/8,13.0000,13.5000,[v1,staff1])-note(n65,79,6420,6614,77,1,1). -snote(n70,[G,n],3,7:2,0,1/4,13.0000,14.0000,[v5,staff2])-note(n64,55,6416,6610,60,1,1). -snote(n68,[B,n],4,7:2,1/8,1/8,13.5000,14.0000,[v1,staff1])-note(n66,71,6643,6850,69,1,1). -snote(n71,[C,n],5,8:1,0,1/4,14.0000,15.0000,[v1,staff1])-note(n67,72,6898,7487,51,1,1). -snote(n73,[C,n],4,8:1,0,1/4,14.0000,15.0000,[v5,staff2])-note(n68,60,6911,7028,49,1,1). -snote(n74,[C,n],3,8:2,0,1/4,15.0000,16.0000,[v5,staff2])-note(n69,48,7434,7484,42,1,1). -snote(n75,[A,n],5,9:1,0,1/16,16.0000,16.2500,[v1,staff1])-note(n71,81,7928,7983,53,1,1). -snote(n83,[E,n],4,9:1,0,1/4,16.0000,17.0000,[v5,staff2])-note(n70,64,7920,7951,63,1,1). -snote(n76,[G,n],5,9:1,1/16,1/16,16.2500,16.5000,[v1,staff1])-note(n72,79,8012,8083,61,1,1). -snote(n77,[F,#],5,9:1,1/8,1/16,16.5000,16.7500,[v1,staff1])-note(n73,78,8126,8180,67,1,1). -snote(n78,[G,n],5,9:1,3/16,1/16,16.7500,17.0000,[v1,staff1])-note(n74,79,8258,8298,53,1,1). -snote(n79,[F,#],5,9:2,0,1/16,17.0000,17.2500,[v1,staff1])-note(n75,78,8358,8440,52,1,1). -snote(n84,[G,n],3,9:2,0,1/4,17.0000,18.0000,[v5,staff2])-note(n76,55,8362,8437,54,1,1). -snote(n80,[G,n],5,9:2,1/16,1/16,17.2500,17.5000,[v1,staff1])-note(n77,79,8459,8619,45,1,1). -snote(n81,[A,n],5,9:2,1/8,1/16,17.5000,17.7500,[v1,staff1])-note(n78,81,8565,8679,64,1,1). -snote(n82,[G,n],5,9:2,3/16,1/16,17.7500,18.0000,[v1,staff1])-note(n79,79,8673,8701,26,1,1). -snote(n85,[G,n],5,10:1,0,1/16,18.0000,18.2500,[v1,staff1])-note(n81,79,8812,8915,73,1,1). -snote(n93,[D,n],4,10:1,0,1/4,18.0000,19.0000,[v5,staff2])-note(n80,62,8806,8866,68,1,1). -snote(n86,[F,n],5,10:1,1/16,1/16,18.2500,18.5000,[v1,staff1])-note(n82,77,8931,8989,55,1,1). -snote(n87,[E,n],5,10:1,1/8,1/16,18.5000,18.7500,[v1,staff1])-note(n83,76,9002,9070,72,1,1). -snote(n88,[F,n],5,10:1,3/16,1/16,18.7500,19.0000,[v1,staff1])-note(n84,77,9126,9180,54,1,1). -snote(n89,[E,n],5,10:2,0,1/16,19.0000,19.2500,[v1,staff1])-note(n85,76,9206,9295,65,1,1). -snote(n94,[G,n],3,10:2,0,1/4,19.0000,20.0000,[v5,staff2])-note(n86,55,9256,9354,49,1,1). -snote(n90,[F,n],5,10:2,1/16,1/16,19.2500,19.5000,[v1,staff1])-note(n87,77,9325,9384,52,1,1). -snote(n91,[G,n],5,10:2,1/8,1/16,19.5000,19.7500,[v1,staff1])-note(n88,79,9415,9492,63,1,1). -snote(n92,[F,n],5,10:2,3/16,1/16,19.7500,20.0000,[v1,staff1])-note(n89,77,9500,9542,52,1,1). -snote(n95,[F,n],5,11:1,0,1/16,20.0000,20.2500,[v1,staff1])-note(n90,77,9649,9768,65,1,1). -snote(n103,[C,n],4,11:1,0,1/4,20.0000,21.0000,[v5,staff2])-note(n91,60,9665,9719,65,1,1). -snote(n96,[E,n],5,11:1,1/16,1/16,20.2500,20.5000,[v1,staff1])-note(n92,76,9763,9859,67,1,1). -snote(n97,[D,#],5,11:1,1/8,1/16,20.5000,20.7500,[v1,staff1])-note(n93,75,9870,9956,64,1,1). -snote(n98,[E,n],5,11:1,3/16,1/16,20.7500,21.0000,[v1,staff1])-note(n94,76,9988,10056,55,1,1). -snote(n99,[D,#],5,11:2,0,1/16,21.0000,21.2500,[v1,staff1])-note(n96,75,10090,10183,49,1,1). -snote(n104,[G,n],3,11:2,0,1/4,21.0000,22.0000,[v5,staff2])-note(n95,55,10085,10192,60,1,1). -snote(n100,[E,n],5,11:2,1/16,1/16,21.2500,21.5000,[v1,staff1])-note(n97,76,10205,10265,58,1,1). -snote(n101,[F,n],5,11:2,1/8,1/16,21.5000,21.7500,[v1,staff1])-note(n98,77,10313,10408,65,1,1). -snote(n102,[E,n],5,11:2,3/16,1/16,21.7500,22.0000,[v1,staff1])-note(n99,76,10394,10424,52,1,1). -snote(n105,[E,n],5,12:1,0,1/16,22.0000,22.2500,[v1,staff1])-note(n100,76,10527,10607,78,1,1). -snote(n113,[F,n],4,12:1,0,1/4,22.0000,23.0000,[v5,staff2])-note(n101,65,10540,10615,63,1,1). -snote(n106,[D,n],5,12:1,1/16,1/16,22.2500,22.5000,[v1,staff1])-note(n102,74,10646,10711,58,1,1). -snote(n107,[C,#],5,12:1,1/8,1/16,22.5000,22.7500,[v1,staff1])-note(n103,73,10755,10818,55,1,1). -snote(n108,[D,n],5,12:1,3/16,1/16,22.7500,23.0000,[v1,staff1])-note(n104,74,10870,10910,48,1,1). -snote(n109,[C,#],5,12:2,0,1/16,23.0000,23.2500,[v1,staff1])-note(n105,73,10958,11052,55,1,1). -snote(n114,[G,n],3,12:2,0,1/4,23.0000,24.0000,[v5,staff2])-note(n106,55,10993,11084,49,1,1). -snote(n110,[D,n],5,12:2,1/16,1/16,23.2500,23.5000,[v1,staff1])-note(n107,74,11095,11115,39,1,1). -snote(n111,[E,n],5,12:2,1/8,1/16,23.5000,23.7500,[v1,staff1])-note(n108,76,11161,11217,55,1,1). -snote(n112,[D,n],5,12:2,3/16,1/16,23.7500,24.0000,[v1,staff1])-note(n109,74,11233,11283,61,1,1). -snote(n115,[A,n],5,13:1,0,1/16,24.0000,24.2500,[v1,staff1])-note(n112,81,11420,11494,67,1,1). -snote(n123,[G,n],3,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n111,55,11406,12019,53,1,1). -snote(n124,[E,n],4,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n110,64,11400,12020,60,1,1). -snote(n116,[G,n],5,13:1,1/16,1/16,24.2500,24.5000,[v1,staff1])-note(n113,79,11536,11590,66,1,1). -snote(n117,[F,#],5,13:1,1/8,1/16,24.5000,24.7500,[v1,staff1])-note(n114,78,11643,11701,52,1,1). -snote(n118,[G,n],5,13:1,3/16,1/16,24.7500,25.0000,[v1,staff1])-note(n115,79,11731,11808,69,1,1). -snote(n119,[E,n],6,13:2,0,1/16,25.0000,25.2500,[v1,staff1])-note(n116,88,11851,11976,77,1,1). -snote(n120,[C,n],6,13:2,1/16,1/16,25.2500,25.5000,[v1,staff1])-note(n117,84,11963,12107,58,1,1). -snote(n121,[A,n],5,13:2,1/8,1/16,25.5000,25.7500,[v1,staff1])-note(n118,81,12068,12150,66,1,1). -snote(n122,[G,n],5,13:2,3/16,1/16,25.7500,26.0000,[v1,staff1])-note(n119,79,12149,12197,64,1,1). -snote(n125,[G,n],5,14:1,0,1/16,26.0000,26.2500,[v1,staff1])-note(n122,79,12315,12393,75,1,1). -snote(n133,[G,n],3,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n120,55,12304,12924,54,1,1). -snote(n134,[D,n],4,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n121,62,12313,12949,61,1,1). -snote(n126,[F,n],5,14:1,1/16,1/16,26.2500,26.5000,[v1,staff1])-note(n123,77,12425,12501,73,1,1). -snote(n127,[E,n],5,14:1,1/8,1/16,26.5000,26.7500,[v1,staff1])-note(n124,76,12548,12614,53,1,1). -snote(n128,[F,n],5,14:1,3/16,1/16,26.7500,27.0000,[v1,staff1])-note(n125,77,12643,12723,77,1,1). -snote(n129,[D,n],6,14:2,0,1/16,27.0000,27.2500,[v1,staff1])-note(n126,86,12762,12910,79,1,1). -snote(n130,[B,n],5,14:2,1/16,1/16,27.2500,27.5000,[v1,staff1])-note(n127,83,12884,13010,59,1,1). -snote(n131,[G,n],5,14:2,1/8,1/16,27.5000,27.7500,[v1,staff1])-note(n128,79,12988,13049,64,1,1). -snote(n132,[F,n],5,14:2,3/16,1/16,27.7500,28.0000,[v1,staff1])-note(n129,77,13061,13104,62,1,1). -snote(n135,[F,n],5,15:1,0,1/16,28.0000,28.2500,[v1,staff1])-note(n130,77,13215,13299,64,1,1). -snote(n143,[G,n],3,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n131,55,13227,13754,45,1,1). -snote(n144,[C,n],4,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n132,60,13229,13761,42,1,1). -snote(n136,[E,n],5,15:1,1/16,1/16,28.2500,28.5000,[v1,staff1])-note(n133,76,13343,13394,52,1,1). -snote(n137,[D,#],5,15:1,1/8,1/16,28.5000,28.7500,[v1,staff1])-note(n134,75,13449,13547,61,1,1). -snote(n138,[E,n],5,15:1,3/16,1/16,28.7500,29.0000,[v1,staff1])-note(n135,76,13570,13709,58,1,1). -snote(n139,[C,n],6,15:2,0,1/16,29.0000,29.2500,[v1,staff1])-note(n136,84,13666,13790,67,1,1). -insertion-note(n137,81,13852,13931,23,1,1). -snote(n140,[G,n],5,15:2,1/16,1/16,29.2500,29.5000,[v1,staff1])-note(n138,79,13854,13892,31,1,1). -snote(n141,[F,n],5,15:2,1/8,1/16,29.5000,29.7500,[v1,staff1])-note(n139,77,13900,13997,55,1,1). -snote(n142,[E,n],5,15:2,3/16,1/16,29.7500,30.0000,[v1,staff1])-note(n141,76,14007,14217,62,1,1). -snote(n146,[C,n],4,15:2,3/16,1/16,29.7500,30.0000,[v5,staff2])-note(n140,60,13990,14130,52,1,1). -snote(n147,[G,n],5,16:1,0,3/16,30.0000,30.7500,[v1,staff1])-note(n144,79,14219,14738,68,1,1). -snote(n150,[E,n],4,16:1,0,3/16,30.0000,30.7500,[v5,staff2])-note(n143,64,14198,14709,44,1,1). -snote(n153,[G,n],3,16:1,0,1/2,30.0000,32.0000,[v6,staff2])-note(n142,55,14181,14897,45,1,1). -snote(n148,[E,n],5,16:1,3/16,1/16,30.7500,31.0000,[v1,staff1])-note(n146,76,14733,14901,47,1,1). -snote(n151,[C,n],4,16:1,3/16,1/16,30.7500,31.0000,[v5,staff2])-note(n145,60,14730,14872,43,1,1). -snote(n149,[D,n],5,16:2,0,1/4,31.0000,32.0000,[v1,staff1])-note(n148,74,14894,15140,44,1,1). -snote(n152,[B,n],3,16:2,0,1/4,31.0000,32.0000,[v5,staff2])-note(n147,59,14892,15103,51,1,1). -snote(n154,[D,n],5,17:1,0,1/16,32.0000,32.2500,[v1,staff1])-note(n150,74,15590,15672,58,1,1). -snote(n162,[C,n],3,17:1,0,1/4,32.0000,33.0000,[v5,staff2])-note(n149,48,15585,15661,61,1,1). -snote(n155,[C,n],5,17:1,1/16,1/16,32.2500,32.5000,[v1,staff1])-note(n151,72,15720,15756,39,1,1). -snote(n156,[B,n],4,17:1,1/8,1/16,32.5000,32.7500,[v1,staff1])-note(n152,71,15800,15856,54,1,1). -snote(n157,[C,n],5,17:1,3/16,1/16,32.7500,33.0000,[v1,staff1])-note(n153,72,15895,15993,55,1,1). -snote(n158,[B,n],4,17:2,0,1/16,33.0000,33.2500,[v1,staff1])-note(n155,71,16026,16096,48,1,1). -snote(n163,[C,n],4,17:2,0,1/4,33.0000,34.0000,[v5,staff2])-note(n154,60,16014,16068,59,1,1). -snote(n159,[C,n],5,17:2,1/16,1/16,33.2500,33.5000,[v1,staff1])-note(n156,72,16170,16214,43,1,1). -snote(n160,[B,n],4,17:2,1/8,1/16,33.5000,33.7500,[v1,staff1])-note(n157,71,16255,16315,59,1,1). -snote(n161,[C,n],5,17:2,3/16,1/16,33.7500,34.0000,[v1,staff1])-note(n158,72,16355,16377,49,1,1). -snote(n164,[A,n],5,18:1,0,1/16,34.0000,34.2500,[v1,staff1])-note(n160,81,16460,16530,25,1,1). -snote(n172,[E,n],4,18:1,0,1/4,34.0000,35.0000,[v5,staff2])-note(n159,64,16437,16500,59,1,1). -snote(n165,[G,n],5,18:1,1/16,1/16,34.2500,34.5000,[v1,staff1])-note(n161,79,16576,16647,63,1,1). -snote(n166,[F,#],5,18:1,1/8,1/16,34.5000,34.7500,[v1,staff1])-note(n162,78,16674,16734,58,1,1). -snote(n167,[G,n],5,18:1,3/16,1/16,34.7500,35.0000,[v1,staff1])-note(n163,79,16802,16826,42,1,1). -snote(n168,[F,#],5,18:2,0,1/16,35.0000,35.2500,[v1,staff1])-note(n164,78,16897,16950,54,1,1). -snote(n173,[C,n],4,18:2,0,1/4,35.0000,36.0000,[v5,staff2])-note(n165,60,16900,16961,47,1,1). -snote(n169,[G,n],5,18:2,1/16,1/16,35.2500,35.5000,[v1,staff1])-note(n166,79,17000,17030,44,1,1). -snote(n170,[F,#],5,18:2,1/8,1/16,35.5000,35.7500,[v1,staff1])-note(n167,78,17095,17149,55,1,1). -snote(n171,[G,n],5,18:2,3/16,1/16,35.7500,36.0000,[v1,staff1])-note(n168,79,17227,17239,32,1,1). -snote(n174,[G,#],5,19:1,0,1/16,36.0000,36.2500,[v1,staff1])-note(n169,80,17291,17379,60,1,1). -snote(n182,[F,n],4,19:1,0,1/4,36.0000,37.0000,[v5,staff2])-note(n170,65,17318,17379,50,1,1). -snote(n175,[A,n],5,19:1,1/16,1/16,36.2500,36.5000,[v1,staff1])-note(n171,81,17413,17516,64,1,1). -snote(n176,[C,n],6,19:1,1/8,1/16,36.5000,36.7500,[v1,staff1])-note(n172,84,17515,17625,60,1,1). -snote(n177,[B,n],5,19:1,3/16,1/16,36.7500,37.0000,[v1,staff1])-note(n173,83,17639,17715,44,1,1). -snote(n178,[D,n],6,19:2,0,1/16,37.0000,37.2500,[v1,staff1])-note(n174,86,17721,17846,61,1,1). -snote(n183,[C,n],4,19:2,0,1/4,37.0000,38.0000,[v5,staff2])-note(n175,60,17737,17822,46,1,1). -snote(n179,[C,n],6,19:2,1/16,1/16,37.2500,37.5000,[v1,staff1])-note(n176,84,17871,17948,42,1,1). -snote(n180,[B,n],5,19:2,1/8,1/16,37.5000,37.7500,[v1,staff1])-note(n177,83,17953,18033,55,1,1). -snote(n181,[A,n],5,19:2,3/16,1/16,37.7500,38.0000,[v1,staff1])-note(n178,81,18035,18067,58,1,1). -snote(n184,[A,n],5,20:1,0,1/16,38.0000,38.2500,[v1,staff1])-note(n181,81,18201,18252,55,1,1). -snote(n192,[C,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n179,60,18194,18582,48,1,1). -snote(n193,[E,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n180,64,18201,18543,55,1,1). -snote(n185,[G,n],5,20:1,1/16,1/16,38.2500,38.5000,[v1,staff1])-note(n182,79,18290,18385,67,1,1). -snote(n186,[E,n],6,20:1,1/8,1/16,38.5000,38.7500,[v1,staff1])-note(n183,88,18420,18561,66,1,1). -snote(n187,[D,n],6,20:1,3/16,1/16,38.7500,39.0000,[v1,staff1])-note(n184,86,18532,18660,63,1,1). -snote(n188,[C,n],6,20:2,0,1/16,39.0000,39.2500,[v1,staff1])-note(n185,84,18632,18742,71,1,1). -snote(n189,[B,n],5,20:2,1/16,1/16,39.2500,39.5000,[v1,staff1])-note(n186,83,18721,18794,68,1,1). -snote(n190,[A,n],5,20:2,1/8,1/16,39.5000,39.7500,[v1,staff1])-note(n187,81,18813,18856,60,1,1). -snote(n191,[G,n],5,20:2,3/16,1/16,39.7500,40.0000,[v1,staff1])-note(n189,79,18948,18977,71,1,1). -snote(n195,[C,#],4,20:2,3/16,1/16,39.7500,40.0000,[v5,staff2])-note(n188,61,18940,18986,58,1,1). -snote(n196,[G,n],5,21:1,0,1/16,40.0000,40.2500,[v1,staff1])-note(n190,79,19065,19157,77,1,1). -snote(n204,[D,n],4,21:1,0,1/4,40.0000,41.0000,[v5,staff2])-note(n191,62,19087,19522,74,1,1). -snote(n197,[F,n],5,21:1,1/16,1/16,40.2500,40.5000,[v1,staff1])-note(n192,77,19185,19289,84,1,1). -snote(n198,[D,n],6,21:1,1/8,1/16,40.5000,40.7500,[v1,staff1])-note(n193,86,19293,19411,75,1,1). -snote(n199,[C,n],6,21:1,3/16,1/16,40.7500,41.0000,[v1,staff1])-note(n194,84,19409,19543,62,1,1). -snote(n200,[B,n],5,21:2,0,1/16,41.0000,41.2500,[v1,staff1])-note(n195,83,19506,19618,71,1,1). -snote(n201,[A,n],5,21:2,1/16,1/16,41.2500,41.5000,[v1,staff1])-note(n196,81,19616,19671,45,1,1). -snote(n202,[G,n],5,21:2,1/8,1/16,41.5000,41.7500,[v1,staff1])-note(n197,79,19679,19734,70,1,1). -snote(n203,[F,n],5,21:2,3/16,1/16,41.7500,42.0000,[v1,staff1])-note(n198,77,19810,19845,81,1,1). -snote(n206,[B,n],3,21:2,3/16,1/16,41.7500,42.0000,[v5,staff2])-note(n199,59,19840,19876,47,1,1). -snote(n207,[F,n],5,22:1,0,1/16,42.0000,42.2500,[v1,staff1])-note(n200,77,19948,20032,77,1,1). -snote(n215,[C,n],4,22:1,0,1/4,42.0000,43.0000,[v5,staff2])-note(n201,60,19981,20353,69,1,1). -snote(n208,[E,n],5,22:1,1/16,1/16,42.2500,42.5000,[v1,staff1])-note(n202,76,20053,20156,77,1,1). -snote(n209,[C,n],6,22:1,1/8,1/16,42.5000,42.7500,[v1,staff1])-note(n203,84,20176,20270,68,1,1). -snote(n210,[B,n],5,22:1,3/16,1/16,42.7500,43.0000,[v1,staff1])-note(n204,83,20276,20417,62,1,1). -snote(n211,[A,n],5,22:2,0,1/16,43.0000,43.2500,[v1,staff1])-note(n205,81,20379,20485,59,1,1). -snote(n212,[G,n],5,22:2,1/16,1/16,43.2500,43.5000,[v1,staff1])-note(n206,79,20462,20534,71,1,1). -snote(n213,[F,n],5,22:2,1/8,1/16,43.5000,43.7500,[v1,staff1])-note(n207,77,20557,20635,72,1,1). -snote(n214,[E,n],5,22:2,3/16,1/16,43.7500,44.0000,[v1,staff1])-note(n208,76,20663,20743,76,1,1). -snote(n217,[A,n],3,22:2,3/16,1/16,43.7500,44.0000,[v5,staff2])-note(n209,57,20675,20787,61,1,1). -snote(n218,[D,n],5,23:1,0,1/8,44.0000,44.5000,[v1,staff1])-note(n210,74,20796,21067,64,1,1). -snote(n222,[F,n],3,23:1,0,1/4,44.0000,45.0000,[v5,staff2])-note(n211,53,20854,21113,72,1,1). -snote(n219,[A,n],5,23:1,1/8,1/8,44.5000,45.0000,[v1,staff1])-note(n212,81,21064,21476,68,1,1). -snote(n220,[G,n],5,23:2,0,1/8,45.0000,45.5000,[v1,staff1])-note(n214,79,21296,21543,75,1,1). -snote(n223,[G,n],3,23:2,0,1/4,45.0000,46.0000,[v5,staff2])-note(n213,55,21287,21555,70,1,1). -snote(n221,[B,n],4,23:2,1/8,1/8,45.5000,46.0000,[v1,staff1])-note(n215,71,21540,21748,58,1,1). -snote(n224,[C,n],5,24:1,0,1/4,46.0000,47.0000,[v1,staff1])-note(n216,72,21793,22915,48,1,1). -snote(n226,[C,n],4,24:1,0,1/4,46.0000,47.0000,[v5,staff2])-note(n217,60,21819,22029,49,1,1). -snote(n227,[C,n],3,24:2,0,1/4,47.0000,48.0000,[v5,staff2])-note(n218,48,22393,22966,50,1,1). -sustain(0,0). -sustain(3316,67). -sustain(3346,127). -sustain(4064,71). -sustain(4094,45). -sustain(4123,0). -sustain(4384,60). -sustain(4412,127). -sustain(4929,73). -sustain(4958,55). -sustain(4987,41). -sustain(5015,0). -sustain(5246,58). -sustain(5276,127). -sustain(5706,60). -sustain(5735,48). -sustain(5763,0). -sustain(7436,69). -sustain(7464,127). -sustain(7866,62). -sustain(7894,44). -sustain(7923,0). -sustain(8385,68). -sustain(8415,127). -sustain(8787,62). -sustain(8817,46). -sustain(8846,0). -sustain(9279,127). -sustain(9566,53). -sustain(9594,40). -sustain(9622,0). -sustain(11006,67). -sustain(11036,127). -sustain(11294,71). -sustain(11323,55). -sustain(11350,0). -sustain(11668,62). -sustain(11698,127). -sustain(12244,53). -sustain(12273,0). -sustain(12590,60). -sustain(12620,127). -sustain(13165,73). -sustain(13194,64). -sustain(13223,53). -sustain(13251,0). -sustain(13511,59). -sustain(13541,127). -sustain(14145,62). -sustain(14174,50). -sustain(14202,37). -sustain(14231,0). -sustain(14923,40). -sustain(14952,67). -sustain(14981,127). -sustain(15498,62). -sustain(15527,53). -sustain(15555,0). -sustain(18262,54). -sustain(18292,71). -sustain(18322,127). -sustain(18955,50). -sustain(18983,0). -sustain(19271,41). -sustain(19301,127). -sustain(19819,57). -sustain(19847,40). -sustain(19876,0). -sustain(20077,43). -sustain(20107,127). -sustain(20682,61). -sustain(20711,50). -sustain(20740,0). +snote(n1,[D,n],5,1:1,0,1/16,0.0000,0.2500,[v1,staff1])-note(n2,74,692,773,72,1,1). +snote(n9,[C,n],3,1:1,0,1/4,0.0000,1.0000,[v5,staff2])-note(n1,48,683,747,70,1,1). +snote(n2,[C,n],5,1:1,1/16,1/16,0.2500,0.5000,[v1,staff1])-note(n3,72,819,892,53,1,1). +snote(n3,[B,n],4,1:1,1/8,1/16,0.5000,0.7500,[v1,staff1])-note(n4,71,914,980,54,1,1). +snote(n4,[C,n],5,1:1,3/16,1/16,0.7500,1.0000,[v1,staff1])-note(n5,72,1009,1098,48,1,1). +snote(n5,[B,n],4,1:2,0,1/16,1.0000,1.2500,[v1,staff1])-note(n7,71,1128,1174,61,1,1). +snote(n10,[C,n],4,1:2,0,1/4,1.0000,2.0000,[v5,staff2])-note(n6,60,1122,1160,67,1,1). +snote(n6,[C,n],5,1:2,1/16,1/16,1.2500,1.5000,[v1,staff1])-note(n8,72,1233,1284,44,1,1). +snote(n7,[B,n],4,1:2,1/8,1/16,1.5000,1.7500,[v1,staff1])-note(n9,71,1316,1410,65,1,1). +snote(n8,[C,n],5,1:2,3/16,1/16,1.7500,2.0000,[v1,staff1])-note(n10,72,1420,1490,61,1,1). +snote(n11,[A,n],5,2:1,0,1/16,2.0000,2.2500,[v1,staff1])-note(n12,81,1556,1637,55,1,1). +snote(n19,[E,n],4,2:1,0,1/4,2.0000,3.0000,[v5,staff2])-note(n11,64,1541,1614,75,1,1). +snote(n12,[G,n],5,2:1,1/16,1/16,2.2500,2.5000,[v1,staff1])-note(n13,79,1683,1752,62,1,1). +snote(n13,[F,#],5,2:1,1/8,1/16,2.5000,2.7500,[v1,staff1])-note(n14,78,1785,1831,52,1,1). +snote(n14,[G,n],5,2:1,3/16,1/16,2.7500,3.0000,[v1,staff1])-note(n15,79,1893,1949,77,1,1). +snote(n15,[F,#],5,2:2,0,1/16,3.0000,3.2500,[v1,staff1])-note(n16,78,1984,2041,60,1,1). +snote(n20,[C,n],4,2:2,0,1/4,3.0000,4.0000,[v5,staff2])-note(n17,60,2002,2043,49,1,1). +snote(n16,[G,n],5,2:2,1/16,1/16,3.2500,3.5000,[v1,staff1])-note(n18,79,2100,2130,52,1,1). +snote(n17,[F,#],5,2:2,1/8,1/16,3.5000,3.7500,[v1,staff1])-note(n19,78,2180,2235,64,1,1). +snote(n18,[G,n],5,2:2,3/16,1/16,3.7500,4.0000,[v1,staff1])-note(n20,79,2275,2317,54,1,1). +snote(n21,[G,#],5,3:1,0,1/16,4.0000,4.2500,[v1,staff1])-note(n21,80,2392,2454,61,1,1). +snote(n29,[F,n],4,3:1,0,1/4,4.0000,5.0000,[v5,staff2])-note(n22,65,2416,2457,61,1,1). +snote(n22,[A,n],5,3:1,1/16,1/16,4.2500,4.5000,[v1,staff1])-note(n23,81,2495,2600,61,1,1). +snote(n23,[C,n],6,3:1,1/8,1/16,4.5000,4.7500,[v1,staff1])-note(n24,84,2611,2722,62,1,1). +snote(n24,[B,n],5,3:1,3/16,1/16,4.7500,5.0000,[v1,staff1])-note(n25,83,2722,2796,51,1,1). +snote(n25,[D,n],6,3:2,0,1/16,5.0000,5.2500,[v1,staff1])-note(n26,86,2807,2939,61,1,1). +snote(n30,[C,n],4,3:2,0,1/4,5.0000,6.0000,[v5,staff2])-note(n27,60,2836,2890,45,1,1). +snote(n26,[C,n],6,3:2,1/16,1/16,5.2500,5.5000,[v1,staff1])-note(n28,84,2939,3024,55,1,1). +snote(n27,[B,n],5,3:2,1/8,1/16,5.5000,5.7500,[v1,staff1])-note(n29,83,3033,3093,64,1,1). +snote(n28,[A,n],5,3:2,3/16,1/16,5.7500,6.0000,[v1,staff1])-note(n30,81,3138,3162,52,1,1). +snote(n31,[A,n],5,4:1,0,1/16,6.0000,6.2500,[v1,staff1])-note(n32,81,3280,3364,68,1,1). +snote(n39,[C,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n31,60,3275,3538,42,1,1). +snote(n40,[E,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n33,64,3291,3526,59,1,1). +snote(n32,[G,n],5,4:1,1/16,1/16,6.2500,6.5000,[v1,staff1])-note(n34,79,3413,3499,62,1,1). +snote(n33,[E,n],6,4:1,1/8,1/16,6.5000,6.7500,[v1,staff1])-note(n35,88,3531,3657,69,1,1). +snote(n34,[D,n],6,4:1,3/16,1/16,6.7500,7.0000,[v1,staff1])-note(n36,86,3639,3762,74,1,1). +snote(n35,[C,n],6,4:2,0,1/16,7.0000,7.2500,[v1,staff1])-note(n37,84,3758,3859,62,1,1). +snote(n36,[B,n],5,4:2,1/16,1/16,7.2500,7.5000,[v1,staff1])-note(n38,83,3845,3912,64,1,1). +snote(n37,[A,n],5,4:2,1/8,1/16,7.5000,7.7500,[v1,staff1])-note(n39,81,3933,3976,59,1,1). +snote(n38,[G,n],5,4:2,3/16,1/16,7.7500,8.0000,[v1,staff1])-note(n41,79,4058,4069,58,1,1). +snote(n42,[C,#],4,4:2,3/16,1/16,7.7500,8.0000,[v5,staff2])-note(n40,61,4050,4094,60,1,1). +snote(n43,[G,n],5,5:1,0,1/16,8.0000,8.2500,[v1,staff1])-note(n42,79,4187,4262,63,1,1). +snote(n51,[D,n],4,5:1,0,1/4,8.0000,9.0000,[v5,staff2])-note(n43,62,4203,4563,79,1,1). +snote(n44,[F,n],5,5:1,1/16,1/16,8.2500,8.5000,[v1,staff1])-note(n44,77,4289,4381,87,1,1). +snote(n45,[D,n],6,5:1,1/8,1/16,8.5000,8.7500,[v1,staff1])-note(n45,86,4410,4548,80,1,1). +snote(n46,[C,n],6,5:1,3/16,1/16,8.7500,9.0000,[v1,staff1])-note(n46,84,4531,4679,70,1,1). +snote(n47,[B,n],5,5:2,0,1/16,9.0000,9.2500,[v1,staff1])-note(n47,83,4637,4736,67,1,1). +snote(n48,[A,n],5,5:2,1/16,1/16,9.2500,9.5000,[v1,staff1])-note(n48,81,4730,4810,72,1,1). +snote(n49,[G,n],5,5:2,1/8,1/16,9.5000,9.7500,[v1,staff1])-note(n49,79,4838,4876,61,1,1). +snote(n50,[F,n],5,5:2,3/16,1/16,9.7500,10.0000,[v1,staff1])-note(n50,77,4939,4961,68,1,1). +snote(n53,[B,n],3,5:2,3/16,1/16,9.7500,10.0000,[v5,staff2])-note(n51,59,4944,4981,51,1,1). +snote(n54,[F,n],5,6:1,0,1/16,10.0000,10.2500,[v1,staff1])-note(n52,77,5062,5144,79,1,1). +snote(n62,[C,n],4,6:1,0,1/4,10.0000,11.0000,[v5,staff2])-note(n53,60,5099,5501,64,1,1). +snote(n55,[E,n],5,6:1,1/16,1/16,10.2500,10.5000,[v1,staff1])-note(n54,76,5176,5260,72,1,1). +snote(n56,[C,n],6,6:1,1/8,1/16,10.5000,10.7500,[v1,staff1])-note(n55,84,5303,5436,69,1,1). +snote(n57,[B,n],5,6:1,3/16,1/16,10.7500,11.0000,[v1,staff1])-note(n56,83,5407,5552,68,1,1). +snote(n58,[A,n],5,6:2,0,1/16,11.0000,11.2500,[v1,staff1])-note(n57,81,5509,5611,61,1,1). +snote(n59,[G,n],5,6:2,1/16,1/16,11.2500,11.5000,[v1,staff1])-note(n58,79,5596,5677,70,1,1). +snote(n60,[F,n],5,6:2,1/8,1/16,11.5000,11.7500,[v1,staff1])-note(n59,77,5684,5753,82,1,1). +snote(n61,[E,n],5,6:2,3/16,1/16,11.7500,12.0000,[v1,staff1])-note(n61,76,5809,5882,61,1,1). +snote(n64,[A,n],3,6:2,3/16,1/16,11.7500,12.0000,[v5,staff2])-note(n60,57,5793,5854,65,1,1). +snote(n65,[D,n],5,7:1,0,1/8,12.0000,12.5000,[v1,staff1])-note(n62,74,5922,6176,75,1,1). +snote(n69,[F,n],3,7:1,0,1/4,12.0000,13.0000,[v5,staff2])-note(n63,53,5978,6219,63,1,1). +snote(n66,[A,n],5,7:1,1/8,1/8,12.5000,13.0000,[v1,staff1])-note(n64,81,6197,6454,70,1,1). +snote(n67,[G,n],5,7:2,0,1/8,13.0000,13.5000,[v1,staff1])-note(n66,79,6420,6614,77,1,1). +snote(n70,[G,n],3,7:2,0,1/4,13.0000,14.0000,[v5,staff2])-note(n65,55,6416,6610,60,1,1). +snote(n68,[B,n],4,7:2,1/8,1/8,13.5000,14.0000,[v1,staff1])-note(n67,71,6643,6850,69,1,1). +snote(n71,[C,n],5,8:1,0,1/4,14.0000,15.0000,[v1,staff1])-note(n68,72,6898,7487,51,1,1). +snote(n73,[C,n],4,8:1,0,1/4,14.0000,15.0000,[v5,staff2])-note(n69,60,6911,7028,49,1,1). +snote(n74,[C,n],3,8:2,0,1/4,15.0000,16.0000,[v5,staff2])-note(n70,48,7434,7484,42,1,1). +snote(n75,[A,n],5,9:1,0,1/16,16.0000,16.2500,[v1,staff1])-note(n72,81,7928,7983,53,1,1). +snote(n83,[E,n],4,9:1,0,1/4,16.0000,17.0000,[v5,staff2])-note(n71,64,7920,7951,63,1,1). +snote(n76,[G,n],5,9:1,1/16,1/16,16.2500,16.5000,[v1,staff1])-note(n73,79,8012,8083,61,1,1). +snote(n77,[F,#],5,9:1,1/8,1/16,16.5000,16.7500,[v1,staff1])-note(n74,78,8126,8180,67,1,1). +snote(n78,[G,n],5,9:1,3/16,1/16,16.7500,17.0000,[v1,staff1])-note(n75,79,8258,8298,53,1,1). +snote(n79,[F,#],5,9:2,0,1/16,17.0000,17.2500,[v1,staff1])-note(n76,78,8358,8440,52,1,1). +snote(n84,[G,n],3,9:2,0,1/4,17.0000,18.0000,[v5,staff2])-note(n77,55,8362,8437,54,1,1). +snote(n80,[G,n],5,9:2,1/16,1/16,17.2500,17.5000,[v1,staff1])-note(n78,79,8459,8619,45,1,1). +snote(n81,[A,n],5,9:2,1/8,1/16,17.5000,17.7500,[v1,staff1])-note(n79,81,8565,8679,64,1,1). +snote(n82,[G,n],5,9:2,3/16,1/16,17.7500,18.0000,[v1,staff1])-note(n80,79,8673,8701,26,1,1). +snote(n85,[G,n],5,10:1,0,1/16,18.0000,18.2500,[v1,staff1])-note(n82,79,8812,8915,73,1,1). +snote(n93,[D,n],4,10:1,0,1/4,18.0000,19.0000,[v5,staff2])-note(n81,62,8806,8866,68,1,1). +snote(n86,[F,n],5,10:1,1/16,1/16,18.2500,18.5000,[v1,staff1])-note(n83,77,8931,8989,55,1,1). +snote(n87,[E,n],5,10:1,1/8,1/16,18.5000,18.7500,[v1,staff1])-note(n84,76,9002,9070,72,1,1). +snote(n88,[F,n],5,10:1,3/16,1/16,18.7500,19.0000,[v1,staff1])-note(n85,77,9126,9180,54,1,1). +snote(n89,[E,n],5,10:2,0,1/16,19.0000,19.2500,[v1,staff1])-note(n86,76,9206,9295,65,1,1). +snote(n94,[G,n],3,10:2,0,1/4,19.0000,20.0000,[v5,staff2])-note(n87,55,9256,9354,49,1,1). +snote(n90,[F,n],5,10:2,1/16,1/16,19.2500,19.5000,[v1,staff1])-note(n88,77,9325,9384,52,1,1). +snote(n91,[G,n],5,10:2,1/8,1/16,19.5000,19.7500,[v1,staff1])-note(n89,79,9415,9492,63,1,1). +snote(n92,[F,n],5,10:2,3/16,1/16,19.7500,20.0000,[v1,staff1])-note(n90,77,9500,9542,52,1,1). +snote(n95,[F,n],5,11:1,0,1/16,20.0000,20.2500,[v1,staff1])-note(n91,77,9649,9768,65,1,1). +snote(n103,[C,n],4,11:1,0,1/4,20.0000,21.0000,[v5,staff2])-note(n92,60,9665,9719,65,1,1). +snote(n96,[E,n],5,11:1,1/16,1/16,20.2500,20.5000,[v1,staff1])-note(n93,76,9763,9859,67,1,1). +snote(n97,[D,#],5,11:1,1/8,1/16,20.5000,20.7500,[v1,staff1])-note(n94,75,9870,9956,64,1,1). +snote(n98,[E,n],5,11:1,3/16,1/16,20.7500,21.0000,[v1,staff1])-note(n95,76,9988,10056,55,1,1). +snote(n99,[D,#],5,11:2,0,1/16,21.0000,21.2500,[v1,staff1])-note(n97,75,10090,10183,49,1,1). +snote(n104,[G,n],3,11:2,0,1/4,21.0000,22.0000,[v5,staff2])-note(n96,55,10085,10192,60,1,1). +snote(n100,[E,n],5,11:2,1/16,1/16,21.2500,21.5000,[v1,staff1])-note(n98,76,10205,10265,58,1,1). +snote(n101,[F,n],5,11:2,1/8,1/16,21.5000,21.7500,[v1,staff1])-note(n99,77,10313,10408,65,1,1). +snote(n102,[E,n],5,11:2,3/16,1/16,21.7500,22.0000,[v1,staff1])-note(n100,76,10394,10424,52,1,1). +snote(n105,[E,n],5,12:1,0,1/16,22.0000,22.2500,[v1,staff1])-note(n101,76,10527,10607,78,1,1). +snote(n113,[F,n],4,12:1,0,1/4,22.0000,23.0000,[v5,staff2])-note(n102,65,10540,10615,63,1,1). +snote(n106,[D,n],5,12:1,1/16,1/16,22.2500,22.5000,[v1,staff1])-note(n103,74,10646,10711,58,1,1). +snote(n107,[C,#],5,12:1,1/8,1/16,22.5000,22.7500,[v1,staff1])-note(n104,73,10755,10818,55,1,1). +snote(n108,[D,n],5,12:1,3/16,1/16,22.7500,23.0000,[v1,staff1])-note(n105,74,10870,10910,48,1,1). +snote(n109,[C,#],5,12:2,0,1/16,23.0000,23.2500,[v1,staff1])-note(n106,73,10958,11052,55,1,1). +snote(n114,[G,n],3,12:2,0,1/4,23.0000,24.0000,[v5,staff2])-note(n107,55,10993,11084,49,1,1). +snote(n110,[D,n],5,12:2,1/16,1/16,23.2500,23.5000,[v1,staff1])-note(n108,74,11095,11115,39,1,1). +snote(n111,[E,n],5,12:2,1/8,1/16,23.5000,23.7500,[v1,staff1])-note(n109,76,11161,11217,55,1,1). +snote(n112,[D,n],5,12:2,3/16,1/16,23.7500,24.0000,[v1,staff1])-note(n110,74,11233,11283,61,1,1). +snote(n115,[A,n],5,13:1,0,1/16,24.0000,24.2500,[v1,staff1])-note(n113,81,11420,11494,67,1,1). +snote(n123,[G,n],3,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n112,55,11406,12019,53,1,1). +snote(n124,[E,n],4,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n111,64,11400,12020,60,1,1). +snote(n116,[G,n],5,13:1,1/16,1/16,24.2500,24.5000,[v1,staff1])-note(n114,79,11536,11590,66,1,1). +snote(n117,[F,#],5,13:1,1/8,1/16,24.5000,24.7500,[v1,staff1])-note(n115,78,11643,11701,52,1,1). +snote(n118,[G,n],5,13:1,3/16,1/16,24.7500,25.0000,[v1,staff1])-note(n116,79,11731,11808,69,1,1). +snote(n119,[E,n],6,13:2,0,1/16,25.0000,25.2500,[v1,staff1])-note(n117,88,11851,11976,77,1,1). +snote(n120,[C,n],6,13:2,1/16,1/16,25.2500,25.5000,[v1,staff1])-note(n118,84,11963,12107,58,1,1). +snote(n121,[A,n],5,13:2,1/8,1/16,25.5000,25.7500,[v1,staff1])-note(n119,81,12068,12150,66,1,1). +snote(n122,[G,n],5,13:2,3/16,1/16,25.7500,26.0000,[v1,staff1])-note(n120,79,12149,12197,64,1,1). +snote(n125,[G,n],5,14:1,0,1/16,26.0000,26.2500,[v1,staff1])-note(n123,79,12315,12393,75,1,1). +snote(n133,[G,n],3,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n121,55,12304,12924,54,1,1). +snote(n134,[D,n],4,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n122,62,12313,12949,61,1,1). +snote(n126,[F,n],5,14:1,1/16,1/16,26.2500,26.5000,[v1,staff1])-note(n124,77,12425,12501,73,1,1). +snote(n127,[E,n],5,14:1,1/8,1/16,26.5000,26.7500,[v1,staff1])-note(n125,76,12548,12614,53,1,1). +snote(n128,[F,n],5,14:1,3/16,1/16,26.7500,27.0000,[v1,staff1])-note(n126,77,12643,12723,77,1,1). +snote(n129,[D,n],6,14:2,0,1/16,27.0000,27.2500,[v1,staff1])-note(n127,86,12762,12910,79,1,1). +snote(n130,[B,n],5,14:2,1/16,1/16,27.2500,27.5000,[v1,staff1])-note(n128,83,12884,13010,59,1,1). +snote(n131,[G,n],5,14:2,1/8,1/16,27.5000,27.7500,[v1,staff1])-note(n129,79,12988,13049,64,1,1). +snote(n132,[F,n],5,14:2,3/16,1/16,27.7500,28.0000,[v1,staff1])-note(n130,77,13061,13104,62,1,1). +snote(n135,[F,n],5,15:1,0,1/16,28.0000,28.2500,[v1,staff1])-note(n131,77,13215,13299,64,1,1). +snote(n143,[G,n],3,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n132,55,13227,13754,45,1,1). +snote(n144,[C,n],4,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n133,60,13229,13761,42,1,1). +snote(n136,[E,n],5,15:1,1/16,1/16,28.2500,28.5000,[v1,staff1])-note(n134,76,13343,13394,52,1,1). +snote(n137,[D,#],5,15:1,1/8,1/16,28.5000,28.7500,[v1,staff1])-note(n135,75,13449,13547,61,1,1). +snote(n138,[E,n],5,15:1,3/16,1/16,28.7500,29.0000,[v1,staff1])-note(n136,76,13570,13709,58,1,1). +snote(n139,[C,n],6,15:2,0,1/16,29.0000,29.2500,[v1,staff1])-note(n137,84,13666,13790,67,1,1). +insertion-note(n138,81,13852,13931,23,1,1). +snote(n140,[G,n],5,15:2,1/16,1/16,29.2500,29.5000,[v1,staff1])-note(n139,79,13854,13892,31,1,1). +snote(n141,[F,n],5,15:2,1/8,1/16,29.5000,29.7500,[v1,staff1])-note(n140,77,13900,13997,55,1,1). +snote(n142,[E,n],5,15:2,3/16,1/16,29.7500,30.0000,[v1,staff1])-note(n142,76,14007,14217,62,1,1). +snote(n146,[C,n],4,15:2,3/16,1/16,29.7500,30.0000,[v5,staff2])-note(n141,60,13990,14130,52,1,1). +snote(n147,[G,n],5,16:1,0,3/16,30.0000,30.7500,[v1,staff1])-note(n145,79,14219,14738,68,1,1). +snote(n150,[E,n],4,16:1,0,3/16,30.0000,30.7500,[v5,staff2])-note(n144,64,14198,14709,44,1,1). +snote(n153,[G,n],3,16:1,0,1/2,30.0000,32.0000,[v6,staff2])-note(n143,55,14181,14897,45,1,1). +snote(n148,[E,n],5,16:1,3/16,1/16,30.7500,31.0000,[v1,staff1])-note(n147,76,14733,14901,47,1,1). +snote(n151,[C,n],4,16:1,3/16,1/16,30.7500,31.0000,[v5,staff2])-note(n146,60,14730,14872,43,1,1). +snote(n149,[D,n],5,16:2,0,1/4,31.0000,32.0000,[v1,staff1])-note(n149,74,14894,15140,44,1,1). +snote(n152,[B,n],3,16:2,0,1/4,31.0000,32.0000,[v5,staff2])-note(n148,59,14892,15103,51,1,1). +snote(n154,[D,n],5,17:1,0,1/16,32.0000,32.2500,[v1,staff1])-note(n151,74,15590,15672,58,1,1). +snote(n162,[C,n],3,17:1,0,1/4,32.0000,33.0000,[v5,staff2])-note(n150,48,15585,15661,61,1,1). +snote(n155,[C,n],5,17:1,1/16,1/16,32.2500,32.5000,[v1,staff1])-note(n152,72,15720,15756,39,1,1). +snote(n156,[B,n],4,17:1,1/8,1/16,32.5000,32.7500,[v1,staff1])-note(n153,71,15800,15856,54,1,1). +snote(n157,[C,n],5,17:1,3/16,1/16,32.7500,33.0000,[v1,staff1])-note(n154,72,15895,15993,55,1,1). +snote(n158,[B,n],4,17:2,0,1/16,33.0000,33.2500,[v1,staff1])-note(n156,71,16026,16096,48,1,1). +snote(n163,[C,n],4,17:2,0,1/4,33.0000,34.0000,[v5,staff2])-note(n155,60,16014,16068,59,1,1). +snote(n159,[C,n],5,17:2,1/16,1/16,33.2500,33.5000,[v1,staff1])-note(n157,72,16170,16214,43,1,1). +snote(n160,[B,n],4,17:2,1/8,1/16,33.5000,33.7500,[v1,staff1])-note(n158,71,16255,16315,59,1,1). +snote(n161,[C,n],5,17:2,3/16,1/16,33.7500,34.0000,[v1,staff1])-note(n159,72,16355,16377,49,1,1). +snote(n164,[A,n],5,18:1,0,1/16,34.0000,34.2500,[v1,staff1])-note(n161,81,16460,16530,25,1,1). +snote(n172,[E,n],4,18:1,0,1/4,34.0000,35.0000,[v5,staff2])-note(n160,64,16437,16500,59,1,1). +snote(n165,[G,n],5,18:1,1/16,1/16,34.2500,34.5000,[v1,staff1])-note(n162,79,16576,16647,63,1,1). +snote(n166,[F,#],5,18:1,1/8,1/16,34.5000,34.7500,[v1,staff1])-note(n163,78,16674,16734,58,1,1). +snote(n167,[G,n],5,18:1,3/16,1/16,34.7500,35.0000,[v1,staff1])-note(n164,79,16802,16826,42,1,1). +snote(n168,[F,#],5,18:2,0,1/16,35.0000,35.2500,[v1,staff1])-note(n165,78,16897,16950,54,1,1). +snote(n173,[C,n],4,18:2,0,1/4,35.0000,36.0000,[v5,staff2])-note(n166,60,16900,16961,47,1,1). +snote(n169,[G,n],5,18:2,1/16,1/16,35.2500,35.5000,[v1,staff1])-note(n167,79,17000,17030,44,1,1). +snote(n170,[F,#],5,18:2,1/8,1/16,35.5000,35.7500,[v1,staff1])-note(n168,78,17095,17149,55,1,1). +snote(n171,[G,n],5,18:2,3/16,1/16,35.7500,36.0000,[v1,staff1])-note(n169,79,17227,17239,32,1,1). +snote(n174,[G,#],5,19:1,0,1/16,36.0000,36.2500,[v1,staff1])-note(n170,80,17291,17379,60,1,1). +snote(n182,[F,n],4,19:1,0,1/4,36.0000,37.0000,[v5,staff2])-note(n171,65,17318,17379,50,1,1). +snote(n175,[A,n],5,19:1,1/16,1/16,36.2500,36.5000,[v1,staff1])-note(n172,81,17413,17516,64,1,1). +snote(n176,[C,n],6,19:1,1/8,1/16,36.5000,36.7500,[v1,staff1])-note(n173,84,17515,17625,60,1,1). +snote(n177,[B,n],5,19:1,3/16,1/16,36.7500,37.0000,[v1,staff1])-note(n174,83,17639,17715,44,1,1). +snote(n178,[D,n],6,19:2,0,1/16,37.0000,37.2500,[v1,staff1])-note(n175,86,17721,17846,61,1,1). +snote(n183,[C,n],4,19:2,0,1/4,37.0000,38.0000,[v5,staff2])-note(n176,60,17737,17822,46,1,1). +snote(n179,[C,n],6,19:2,1/16,1/16,37.2500,37.5000,[v1,staff1])-note(n177,84,17871,17948,42,1,1). +snote(n180,[B,n],5,19:2,1/8,1/16,37.5000,37.7500,[v1,staff1])-note(n178,83,17953,18033,55,1,1). +snote(n181,[A,n],5,19:2,3/16,1/16,37.7500,38.0000,[v1,staff1])-note(n179,81,18035,18067,58,1,1). +snote(n184,[A,n],5,20:1,0,1/16,38.0000,38.2500,[v1,staff1])-note(n182,81,18201,18252,55,1,1). +snote(n192,[C,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n180,60,18194,18582,48,1,1). +snote(n193,[E,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n181,64,18201,18543,55,1,1). +snote(n185,[G,n],5,20:1,1/16,1/16,38.2500,38.5000,[v1,staff1])-note(n183,79,18290,18385,67,1,1). +snote(n186,[E,n],6,20:1,1/8,1/16,38.5000,38.7500,[v1,staff1])-note(n184,88,18420,18561,66,1,1). +snote(n187,[D,n],6,20:1,3/16,1/16,38.7500,39.0000,[v1,staff1])-note(n185,86,18532,18660,63,1,1). +snote(n188,[C,n],6,20:2,0,1/16,39.0000,39.2500,[v1,staff1])-note(n186,84,18632,18742,71,1,1). +snote(n189,[B,n],5,20:2,1/16,1/16,39.2500,39.5000,[v1,staff1])-note(n187,83,18721,18794,68,1,1). +snote(n190,[A,n],5,20:2,1/8,1/16,39.5000,39.7500,[v1,staff1])-note(n188,81,18813,18856,60,1,1). +snote(n191,[G,n],5,20:2,3/16,1/16,39.7500,40.0000,[v1,staff1])-note(n190,79,18948,18977,71,1,1). +snote(n195,[C,#],4,20:2,3/16,1/16,39.7500,40.0000,[v5,staff2])-note(n189,61,18940,18986,58,1,1). +snote(n196,[G,n],5,21:1,0,1/16,40.0000,40.2500,[v1,staff1])-note(n191,79,19065,19157,77,1,1). +snote(n204,[D,n],4,21:1,0,1/4,40.0000,41.0000,[v5,staff2])-note(n192,62,19087,19522,74,1,1). +snote(n197,[F,n],5,21:1,1/16,1/16,40.2500,40.5000,[v1,staff1])-note(n193,77,19185,19289,84,1,1). +snote(n198,[D,n],6,21:1,1/8,1/16,40.5000,40.7500,[v1,staff1])-note(n194,86,19293,19411,75,1,1). +snote(n199,[C,n],6,21:1,3/16,1/16,40.7500,41.0000,[v1,staff1])-note(n195,84,19409,19543,62,1,1). +snote(n200,[B,n],5,21:2,0,1/16,41.0000,41.2500,[v1,staff1])-note(n196,83,19506,19618,71,1,1). +snote(n201,[A,n],5,21:2,1/16,1/16,41.2500,41.5000,[v1,staff1])-note(n197,81,19616,19671,45,1,1). +snote(n202,[G,n],5,21:2,1/8,1/16,41.5000,41.7500,[v1,staff1])-note(n198,79,19679,19734,70,1,1). +snote(n203,[F,n],5,21:2,3/16,1/16,41.7500,42.0000,[v1,staff1])-note(n199,77,19810,19845,81,1,1). +snote(n206,[B,n],3,21:2,3/16,1/16,41.7500,42.0000,[v5,staff2])-note(n200,59,19840,19876,47,1,1). +snote(n207,[F,n],5,22:1,0,1/16,42.0000,42.2500,[v1,staff1])-note(n201,77,19948,20032,77,1,1). +snote(n215,[C,n],4,22:1,0,1/4,42.0000,43.0000,[v5,staff2])-note(n202,60,19981,20353,69,1,1). +snote(n208,[E,n],5,22:1,1/16,1/16,42.2500,42.5000,[v1,staff1])-note(n203,76,20053,20156,77,1,1). +snote(n209,[C,n],6,22:1,1/8,1/16,42.5000,42.7500,[v1,staff1])-note(n204,84,20176,20270,68,1,1). +snote(n210,[B,n],5,22:1,3/16,1/16,42.7500,43.0000,[v1,staff1])-note(n205,83,20276,20417,62,1,1). +snote(n211,[A,n],5,22:2,0,1/16,43.0000,43.2500,[v1,staff1])-note(n206,81,20379,20485,59,1,1). +snote(n212,[G,n],5,22:2,1/16,1/16,43.2500,43.5000,[v1,staff1])-note(n207,79,20462,20534,71,1,1). +snote(n213,[F,n],5,22:2,1/8,1/16,43.5000,43.7500,[v1,staff1])-note(n208,77,20557,20635,72,1,1). +snote(n214,[E,n],5,22:2,3/16,1/16,43.7500,44.0000,[v1,staff1])-note(n209,76,20663,20743,76,1,1). +snote(n217,[A,n],3,22:2,3/16,1/16,43.7500,44.0000,[v5,staff2])-note(n210,57,20675,20787,61,1,1). +snote(n218,[D,n],5,23:1,0,1/8,44.0000,44.5000,[v1,staff1])-note(n211,74,20796,21067,64,1,1). +snote(n222,[F,n],3,23:1,0,1/4,44.0000,45.0000,[v5,staff2])-note(n212,53,20854,21113,72,1,1). +snote(n219,[A,n],5,23:1,1/8,1/8,44.5000,45.0000,[v1,staff1])-note(n213,81,21064,21476,68,1,1). +snote(n220,[G,n],5,23:2,0,1/8,45.0000,45.5000,[v1,staff1])-note(n215,79,21296,21543,75,1,1). +snote(n223,[G,n],3,23:2,0,1/4,45.0000,46.0000,[v5,staff2])-note(n214,55,21287,21555,70,1,1). +snote(n221,[B,n],4,23:2,1/8,1/8,45.5000,46.0000,[v1,staff1])-note(n216,71,21540,21748,58,1,1). +snote(n224,[C,n],5,24:1,0,1/4,46.0000,47.0000,[v1,staff1])-note(n217,72,21793,22915,48,1,1). +snote(n226,[C,n],4,24:1,0,1/4,46.0000,47.0000,[v5,staff2])-note(n218,60,21819,22029,49,1,1). +snote(n227,[C,n],3,24:2,0,1/4,47.0000,48.0000,[v5,staff2])-note(n219,48,22393,22966,50,1,1). diff --git a/tests/test_match_import.py b/tests/test_match_import.py index 0b703e9f..64356a8a 100644 --- a/tests/test_match_import.py +++ b/tests/test_match_import.py @@ -269,7 +269,8 @@ def test_info_lines(self): audioLastNote_line = "info(audioLastNote,9.8372)." composer_line = "info(composer,Frèdéryk Chopin)." performer_line = "info(performer,A. Human Pianist)." - midiClockUnits_line = "info(midiClockUnits,4000)." + midiClockUnits_line = "info(midiClockUnits,480)." + # midiClockUnits_line = "info(midiClockUnits,4000)." midiClockRate_line = "info(midiClockRate,500000)." approximateTempo_line = "info(approximateTempo,98.2902)." subtitle_line = "info(subtitle,Subtitle)." diff --git a/tests/test_tonal_tension.py b/tests/test_tonal_tension.py index b7a6f970..e80d8b34 100644 --- a/tests/test_tonal_tension.py +++ b/tests/test_tonal_tension.py @@ -26,9 +26,9 @@ class TestTonalTension(unittest.TestCase): def test_prepare_notearray(self): target_note_array = np.array( [ - (0.0, 2.0, 69, 64, "n0", "A", 0, 4, 0, -1), - (1.0, 1.0, 72, 64, "n1", "C", 0, 5, 0, -1), - (1.0, 1.0, 76, 64, "n2", "E", 0, 5, 0, -1), + (0.0, 2.0, 69, 64, "n1", "A", 0, 4, 0, -1), + (1.0, 1.0, 72, 64, "n2", "C", 0, 5, 0, -1), + (1.0, 1.0, 76, 64, "n3", "E", 0, 5, 0, -1), ], dtype=[ ("onset_sec", " Date: Thu, 3 Aug 2023 17:53:24 +0200 Subject: [PATCH 21/80] Minor edit on line break to re-launch tests --- partitura/io/exportmatch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index c75cb82e..f38d48ec 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -207,7 +207,6 @@ def matchfile_from_alignment( # Score prop header lines scoreprop_lines = defaultdict(list) - # For score notes score_info = dict() # Info for sorting lines From 5c1214700ade3168f9b903a9596cda092f139fff Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Thu, 3 Aug 2023 17:59:54 +0200 Subject: [PATCH 22/80] Suggested edit by review. --- partitura/score.py | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/partitura/score.py b/partitura/score.py index 76246ba0..1b9cfd71 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -4595,7 +4595,7 @@ def unfold_part_maximal(score: ScoreLike, update_ids=True, ignore_leaps=True): Parameters ---------- - score : :class:`Part` + score : ScoreLike The Part/Score to unfold. update_ids : bool (optional) Update note ids to reflect the repetitions. Note IDs will have @@ -4609,8 +4609,8 @@ def unfold_part_maximal(score: ScoreLike, update_ids=True, ignore_leaps=True): Returns ------- - unfolded_part : :class:`Part` - The unfolded Part + unfolded_part : ScoreLike + The unfolded Part/Score """ if isinstance(score, Score): @@ -4630,47 +4630,39 @@ def unfold_part_maximal(score: ScoreLike, update_ids=True, ignore_leaps=True): return unfolded_part -def unfold_part_minimal(score: ScoreLike, update_ids=True, ignore_leaps=True): +def unfold_part_minimal(score: ScoreLike): """Return the "minimally" unfolded score/part, that is, a copy of the - part where all segments marked with repeat or volta signs are not included. + part where all segments marked with repeat are included only once. For voltas only the last volta segment is included. - + Note this might not be musically valid, e.g. a passing a "fine" even a first time will stop this unfolding. + Warning: The unfolding of repeats is computed part-wise, inconsistent repeat markings of parts of a single result + in inconsistent unfoldings. Parameters ---------- score: ScoreLike The score/part to unfold. - update_ids : bool (optional) - Update note ids to reflect the repetitions. Note IDs will have - a '-', e.g., 'n132-1' and 'n132-2' - represent the first and second repetition of 'n132' in the - input `part`. Defaults to False. - ignore_leaps : bool (optional) - If ignored, repetitions after a leap are unfolded fully. - A leap is a used dal segno, da capo, or al coda marking. - Defaults to True. Returns ------- - unfolded_part : :class:`Part` + unfolded_score : ScoreLike The unfolded Part """ if isinstance(score, Score): - new_score = deepcopy(score) + unfolded_score = deepcopy(score) new_partlist = list() - for part in new_score.parts: - unfolded_part = unfold_part_minimal(part, update_ids=update_ids, ignore_leaps=ignore_leaps) + for part in unfolded_score.parts: + unfolded_part = unfold_part_minimal(part) new_partlist.append(unfolded_part) - new_score.parts = new_partlist - return new_score + unfolded_score.parts = new_partlist + return unfolded_score paths = get_paths( - score, no_repeats=True, all_repeats=True, ignore_leap_info=ignore_leaps - ) + score, no_repeats=True, all_repeats=False, ignore_leap_info=True) - unfolded_part = new_part_from_path(paths[0], score, update_ids=update_ids) - return unfolded_part + unfolded_score = new_part_from_path(paths[0], score, update_ids=False) + return unfolded_score # UPDATED / UNCHANGED VERSION From b9ff8db26e8e44691e930896919103bb59143200 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Fri, 4 Aug 2023 08:10:50 +0200 Subject: [PATCH 23/80] updated load_score test to a better coding style --- partitura/io/importmei.py | 2 +- tests/test_load_score.py | 16 ++++++---------- tests/test_musescore.py | 3 ++- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 520f93fd..7fe82e69 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -73,7 +73,7 @@ def __init__(self, mei_path: PathLike) -> None: # find the music tag inside the document music_el = self.document.findall(self._ns_name("music", all=True)) if len(music_el) != 1: - raise Exception("Only MEI with a single music element are supported") + raise Exception("Only MEI with a single element are supported") self.music_el = music_el[0] self.repetitions = ( [] diff --git a/tests/test_load_score.py b/tests/test_load_score.py index 3ae02011..474e927f 100644 --- a/tests/test_load_score.py +++ b/tests/test_load_score.py @@ -40,13 +40,9 @@ def test_load_score(self): self.check_return_type(fn) def check_return_type(self, fn): - try: - score = load_score(fn) - self.assertTrue(isinstance(score, Score)) - - for pp in score.part_structure: - self.assertTrue(type(pp) in (Part, PartGroup)) - for pp in score.parts: - self.assertTrue(isinstance(pp, Part)) - except NotSupportedFormatError: - self.assertTrue(False, f"Score {fn} failing when parsed.") + score = load_score(fn) + self.assertTrue(isinstance(score, Score), f"results of load_score type are not Score for score {fn}.") + for pp in score.part_structure: + self.assertTrue(type(pp) in (Part, PartGroup), f"results of score.part_structure type are neither Part or PartGroup for score {fn}.") + for pp in score.parts: + self.assertTrue(isinstance(pp, Part), f"results of score.parts type are not Part for score {fn}.") diff --git a/tests/test_musescore.py b/tests/test_musescore.py index 93529ff6..156965a1 100644 --- a/tests/test_musescore.py +++ b/tests/test_musescore.py @@ -19,7 +19,8 @@ import numpy as np from pathlib import Path -# This tests are commented, because it is not possible to run MuseScore on a Github +# This tests are commented, because it is not possible to run MuseScore on Github server +# uncomment to run them locally instead # class TestImportMusescore(unittest.TestCase): # def test_epfl_scores(self): From a5993b0b6983447e904c86181528cc1ab070cb74 Mon Sep 17 00:00:00 2001 From: sildater Date: Fri, 4 Aug 2023 06:17:21 +0000 Subject: [PATCH 24/80] Format code with black (bot) --- partitura/score.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/partitura/score.py b/partitura/score.py index 1b9cfd71..3a0589f0 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -4617,7 +4617,9 @@ def unfold_part_maximal(score: ScoreLike, update_ids=True, ignore_leaps=True): new_score = deepcopy(score) new_partlist = list() for score in new_score.parts: - unfolded_part = unfold_part_maximal(score, update_ids=update_ids, ignore_leaps=ignore_leaps) + unfolded_part = unfold_part_maximal( + score, update_ids=update_ids, ignore_leaps=ignore_leaps + ) new_partlist.append(unfolded_part) new_score.parts = new_partlist return new_score @@ -4658,8 +4660,7 @@ def unfold_part_minimal(score: ScoreLike): unfolded_score.parts = new_partlist return unfolded_score - paths = get_paths( - score, no_repeats=True, all_repeats=False, ignore_leap_info=True) + paths = get_paths(score, no_repeats=True, all_repeats=False, ignore_leap_info=True) unfolded_score = new_part_from_path(paths[0], score, update_ids=False) return unfolded_score From 7fdd5825227bc2e989c591da1ba059fe67ac8baf Mon Sep 17 00:00:00 2001 From: manoskary Date: Fri, 4 Aug 2023 13:20:55 +0000 Subject: [PATCH 25/80] Format code with black (bot) --- partitura/io/importmei.py | 42 +++++++++++++++++++++++++++++---------- partitura/io/musescore.py | 30 ++++++++++++++++++---------- partitura/utils/music.py | 4 ++-- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 7fe82e69..eb8bcaa3 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -367,7 +367,10 @@ def _intsymdur_from_symbolic(self, symbolic_dur): # intsymdur = ( # intsymdur * symbolic_dur["actual_notes"] / symbolic_dur["normal_notes"] # ) - tuplet_modifier = (symbolic_dur["normal_notes"], symbolic_dur["actual_notes"]) + tuplet_modifier = ( + symbolic_dur["normal_notes"], + symbolic_dur["actual_notes"], + ) else: tuplet_modifier = None # deals with dots @@ -381,7 +384,9 @@ def _find_ppq(self): durs_ppq = [] for el in els_with_dur: symbolic_duration = self._get_symbolic_duration(el) - intsymdur, dots, tuplet_mod = self._intsymdur_from_symbolic(symbolic_duration) + intsymdur, dots, tuplet_mod = self._intsymdur_from_symbolic( + symbolic_duration + ) if tuplet_mod is not None: # consider time modifications keeping the numerator of the minimized fraction minimized_fraction = Fraction(intsymdur * tuplet_mod[1], tuplet_mod[0]) @@ -588,10 +593,15 @@ def _duration_info(self, el, part): duration = 0 if el.get("grace") is not None else int(el.get("dur.ppq")) else: # compute the duration from the symbolic duration - intsymdur, dots, tuplet_mod = self._intsymdur_from_symbolic(symbolic_duration) + intsymdur, dots, tuplet_mod = self._intsymdur_from_symbolic( + symbolic_duration + ) divs = part._quarter_durations[0] # divs is the same as ppq if tuplet_mod is None: - tuplet_mod = (1,1) # if no tuplet modifier, set one that does not change the duration + tuplet_mod = ( + 1, + 1, + ) # if no tuplet modifier, set one that does not change the duration duration = (divs * 4 * tuplet_mod[0]) / (intsymdur * tuplet_mod[1]) for d in range(dots): duration = duration + 0.5 * duration @@ -746,7 +756,7 @@ def _handle_mrest(self, mrest_el, position, voice, staff, part): part.add(rest, position, position + parts_per_measure) # return duration to update the position in the layer return position + parts_per_measure - + def _handle_multirest(self, multirest_el, position, voice, staff, part): """ Handles a rest that spawn multiple measures @@ -774,7 +784,9 @@ def _handle_multirest(self, multirest_el, position, voice, staff, part): # find how many measures n_measures = int(multirest_el.attrib["num"]) if n_measures > 1: - raise Exception(f"Multi-rests with more than 1 measure are not supported yet. Found one with {n_measures}.") + raise Exception( + f"Multi-rests with more than 1 measure are not supported yet. Found one with {n_measures}." + ) # find closest time signature last_ts = list(part.iter_all(cls=score.TimeSignature))[-1] # find divs per measure @@ -845,7 +857,9 @@ def _handle_space(self, e, position, part): """Moves current position.""" try: space_id, duration, symbolic_duration = self._duration_info(e, part) - except KeyError: # if the space don't have a duration, move to the end of the measure + except ( + KeyError + ): # if the space don't have a duration, move to the end of the measure # find closest time signature last_ts = list(part.iter_all(cls=score.TimeSignature))[-1] # find divs per measure @@ -897,7 +911,9 @@ def _handle_layer_in_staff_in_measure( new_position = self._handle_mrest( e, position, ind_layer, ind_staff, part ) - elif e.tag == self._ns_name("multiRest"): # rest that spawn more than one measure + elif e.tag == self._ns_name( + "multiRest" + ): # rest that spawn more than one measure new_position = self._handle_multirest( e, position, ind_layer, ind_staff, part ) @@ -1161,12 +1177,18 @@ def _insert_repetitions(self): if sanitized_repetition_list[-1] == "start": print("WARNING : unmatched repetitions. Ignoring last start") ## sanitize the found repetitions to remove duplicates - sanitized_repetition_list = list(OrderedDict((tuple(d.items()), d) for d in sanitized_repetition_list).values()) + sanitized_repetition_list = list( + OrderedDict( + (tuple(d.items()), d) for d in sanitized_repetition_list + ).values() + ) self.repetitions = sanitized_repetition_list ## insert the repetitions to all parts for rep_start, rep_stop in zip(self.repetitions[:-1:2], self.repetitions[1::2]): - assert rep_start["type"] == "start" and rep_stop["type"] == "stop", "Something wrong with repetitions" + assert ( + rep_start["type"] == "start" and rep_stop["type"] == "stop" + ), "Something wrong with repetitions" for part in score.iter_parts(self.parts): part.add(score.Repeat(), rep_start["pos"], rep_stop["pos"]) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 67dd5c1c..6f50e304 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -35,9 +35,10 @@ class MuseScoreNotFoundException(Exception): class FileImportException(Exception): pass + def find_musescore_version(version=4): """Find the path to the MuseScore executable for a specific version. - If version is a empty string it tries to find an unspecified version of + If version is a empty string it tries to find an unspecified version of MuseScore which is used in some systems. """ result = shutil.which(f"musescore{version}") @@ -47,12 +48,17 @@ def find_musescore_version(version=4): if platform.system() == "Linux": pass elif platform.system() == "Darwin": - result = shutil.which(f"/Applications/MuseScore {version}.app/Contents/MacOS/mscore") + result = shutil.which( + f"/Applications/MuseScore {version}.app/Contents/MacOS/mscore" + ) elif platform.system() == "Windows": - result = shutil.which(rf"C:\Program Files\MuseScore {version}\bin\MuseScore{version}.exe") + result = shutil.which( + rf"C:\Program Files\MuseScore {version}\bin\MuseScore{version}.exe" + ) return result + def find_musescore(): """Find the path to the MuseScore executable. @@ -74,11 +80,15 @@ def find_musescore(): if not mscore_exec: mscore_exec = find_musescore_version(version=3) if mscore_exec: - warnings.warn("Only Musescore 3 is installed. Consider upgrading to musescore 4.") + warnings.warn( + "Only Musescore 3 is installed. Consider upgrading to musescore 4." + ) else: mscore_exec = find_musescore_version(version="") if mscore_exec: - warnings.warn("A unspecified version of MuseScore was found. Consider upgrading to musescore 4.") + warnings.warn( + "A unspecified version of MuseScore was found. Consider upgrading to musescore 4." + ) else: raise MuseScoreNotFoundException() return mscore_exec @@ -122,16 +132,16 @@ def load_via_musescore( if filename.endswith(".mscz"): pass else: - # open the file as text and check if the first symbol is "<" to avoid + # open the file as text and check if the first symbol is "<" to avoid # further processing in case of non-XML files - with open(filename, "r") as f: + with open(filename, "r") as f: if f.read(1) != "<": raise FileImportException( "File {} is not a valid XML file.".format(filename) ) - + mscore_exec = find_musescore() - + xml_fh = os.path.splitext(os.path.basename(filename))[0] + ".musicxml" cmd = [mscore_exec, "-o", xml_fh, filename, "-f"] @@ -213,7 +223,7 @@ def render_musescore( "-o", os.fspath(img_fh), os.fspath(xml_fh), - "-f" + "-f", ] try: ps = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index dfb3fdef..5a24b708 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -686,8 +686,8 @@ def midi_ticks_to_seconds( SIGN_TO_ALTER = { "n": 0, - "ns" : 1, - "nf" : -1, + "ns": 1, + "nf": -1, "#": 1, "s": 1, "ss": 2, From 4d73f13d8a41bd7a5c972d2093b69d40c32ecdc9 Mon Sep 17 00:00:00 2001 From: huispaty Date: Tue, 8 Aug 2023 11:50:07 +0200 Subject: [PATCH 26/80] removed pandas dependency and print statements, added score attribute test --- partitura/io/exportmatch.py | 22 ++++++++++--------- partitura/io/exportmidi.py | 2 -- partitura/io/importmidi.py | 19 +--------------- tests/__init__.py | 2 +- .../data/musicxml/test_score_object.musicxml | 1 + tests/test_pianoroll.py | 9 ++++---- tests/test_xml.py | 10 ++++++++- 7 files changed, 29 insertions(+), 36 deletions(-) diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index f38d48ec..684d5fda 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -8,7 +8,6 @@ * The methods only export matchfiles version 1.0.0. """ import numpy as np -import pandas as pd from typing import List, Optional, Iterable @@ -401,14 +400,18 @@ def matchfile_from_alignment( sort_stime = [] note_lines = [] - # Filter out voice overlap notes using a pd.Dataframe structure - spart_df = pd.DataFrame(spart.note_array()) - voice_overlap_candidates = spart_df.groupby(['onset_beat', 'pitch']) - voice_overlap_note_ids = [] - for _, group in voice_overlap_candidates: - if len(group) > 1: - voice_overlap_note_ids.extend(np.array(group.values.tolist())[:,-1].tolist()) - + # Get ids of notes which voice overlap + spart_array = np.array([list(row) for row in spart.note_array()]) + onset_pitch_slice = [tuple(i) for i in list(spart_array[:, [0, 6]])] + + onset_pitch_dict = defaultdict(list) + for index, tup in enumerate(onset_pitch_slice): + onset_pitch_dict[tup].append(index) + duplicates = {key: indices for key, indices in onset_pitch_dict.items() if len(indices) > 1} + + voice_overlap_note_ids = [spart_array[idx][:,-1] for k, idx in duplicates.items()] + voice_overlap_note_ids = [i for sub in voice_overlap_note_ids for i in sub] + for al_note in alignment: label = al_note["label"] @@ -450,7 +453,6 @@ def matchfile_from_alignment( # sort notes by score onset (performed insertions are sorted # according to the interpolation map sort_stime = np.array(sort_stime) - # print(sort_stime) sort_stime_idx = np.lexsort((sort_stime[:, 1], sort_stime[:, 0])) note_lines = np.array(note_lines)[sort_stime_idx] diff --git a/partitura/io/exportmidi.py b/partitura/io/exportmidi.py index 2b52e386..28ce7d83 100644 --- a/partitura/io/exportmidi.py +++ b/partitura/io/exportmidi.py @@ -202,8 +202,6 @@ def save_performance_midi( track_events[tr][min(timepoints)].append( Message("program_change", program=0, channel=ch) ) - # TODOph fix diff i/o notes - print('==== exported midi notes:', len(performed_part.notes)) midi_type = 0 if len(track_events) == 1 else 1 diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index 72082e0d..7cb3ed6c 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -176,31 +176,14 @@ def load_performance_midi( note = note_hash(msg.channel, msg.note) # start note if it's a 'note on' event with velocity > 0 - # key_exists = False if note_on and msg.velocity > 0: - # note_on_ctr += 1 - # if note in sounding_notes.keys(): - # key_exists = True - # print('key_exists') - # print(f'\t{note} {sounding_notes[note]} - existing key') - # print('\tmsg number:', msg_i, msg, 'incoming msg') - # save the onset time and velocity sounding_notes[note] = (t, ttick, msg.velocity) - - # if key_exists: - # print(f'\t{note} {sounding_notes[note]} - overwritten key') - # key_exists = False # end note if it's a 'note off' event or 'note on' with velocity 0 elif note_off or (note_on and msg.velocity == 0): if note not in sounding_notes: - # print('ignoring:') - # print('\tmsg number:', msg_i, msg) - # print(f'\t{note} {(t, ttick, msg.velocity)}') - # print((f"\tMIDI message {msg}")) - warnings.warn(f"ignoring MIDI message {msg}, {note}") - print() + warnings.warn(f"ignoring MIDI message {msg}") continue # append the note to the list associated with the channel diff --git a/tests/__init__.py b/tests/__init__.py index c24bdcd0..6d08b32c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -24,7 +24,7 @@ # yield identical MusicXML MUSICXML_IMPORT_EXPORT_TESTFILES = [ os.path.join(MUSICXML_PATH, fn) - for fn in ["test_note_ties.xml", "test_note_ties_divs.xml"] + for fn in ["test_note_ties.xml", "test_note_ties_divs.xml", "test_score_object.musicxml"] ] MUSICXML_UNFOLD_TESTPAIRS = [ ( diff --git a/tests/data/musicxml/test_score_object.musicxml b/tests/data/musicxml/test_score_object.musicxml index ab3b38f6..84025f84 100644 --- a/tests/data/musicxml/test_score_object.musicxml +++ b/tests/data/musicxml/test_score_object.musicxml @@ -3,6 +3,7 @@ Test Title + Test Opus 1 T. H. E. Composer diff --git a/tests/test_pianoroll.py b/tests/test_pianoroll.py index 18eed529..404a900e 100644 --- a/tests/test_pianoroll.py +++ b/tests/test_pianoroll.py @@ -214,14 +214,12 @@ def test_pianoroll_to_notearray(self): self.assertTrue(test) def test_reconstruction_score(self): - - for fn in MUSICXML_IMPORT_EXPORT_TESTFILES: + for fn in MUSICXML_IMPORT_EXPORT_TESTFILES[:2]: score = load_musicxml(fn) note_array = score[0].note_array() pr = compute_pianoroll( score[0], time_unit="div", time_div=1, remove_silence=False ) - rec_note_array = pianoroll_to_notearray(pr, time_div=1, time_unit="div") original_pitch_idx = np.argsort(note_array["pitch"]) @@ -233,7 +231,7 @@ def test_reconstruction_score(self): rec_note_array = rec_note_array[rec_pitch_idx] rec_onset_idx = np.argsort(rec_note_array["onset_div"], kind="mergesort") rec_note_array = rec_note_array[rec_onset_idx] - + test_pitch = np.all(note_array["pitch"] == rec_note_array["pitch"]) self.assertTrue(test_pitch) test_onset = np.all(note_array["onset_div"] == rec_note_array["onset_div"]) @@ -442,3 +440,6 @@ def test_indices(self): # Onsets and offsets should be identical self.assertTrue(np.all(pr_idxs[:, 2:4] == pcr_idxs[:, 2:4])) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_xml.py b/tests/test_xml.py index 2cca87bc..02438f35 100755 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -55,7 +55,7 @@ class TestMusicXML(unittest.TestCase): """ def test_import_export(self): - for fn in MUSICXML_IMPORT_EXPORT_TESTFILES: + for fn in MUSICXML_IMPORT_EXPORT_TESTFILES[:2]: with open(fn) as f: parts = load_musicxml(f, validate=False) result = save_musicxml(parts).decode("UTF-8") @@ -241,6 +241,14 @@ def _pretty_export_import_pretty_test(self, part1): show_diff(pstring1, pstring2) msg = "Exported and imported score does not yield identical pretty printed representations" self.assertTrue(equal, msg) + + def test_score_attribute(self): + score = load_musicxml(MUSICXML_IMPORT_EXPORT_TESTFILES[2]) + test_work_title = "Test Title" + test_work_number = "Test Opus 1" + + self.assertTrue(score.work_title == test_work_title) + self.assertTrue(score.work_number == test_work_number) def make_part_slur(): From 30ee252203a04c184a1d518f2c3e4946d0b0ea30 Mon Sep 17 00:00:00 2001 From: huispaty Date: Tue, 8 Aug 2023 17:58:38 +0200 Subject: [PATCH 27/80] efficient voice overlap filtering, changed test file access; --- partitura/io/exportmatch.py | 12 +++++++----- partitura/io/matchfile_utils.py | 4 +++- tests/__init__.py | 6 +++++- tests/test_pianoroll.py | 2 +- tests/test_xml.py | 5 +++-- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index 684d5fda..84abe403 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -401,16 +401,18 @@ def matchfile_from_alignment( note_lines = [] # Get ids of notes which voice overlap - spart_array = np.array([list(row) for row in spart.note_array()]) - onset_pitch_slice = [tuple(i) for i in list(spart_array[:, [0, 6]])] + print('12987329412398476') + print(type(spart)) + print(type(spart.note_array())) + onset_pitch_slice = [tuple(i) for i in list(spart.note_array()[["onset_beat", "pitch"]])] onset_pitch_dict = defaultdict(list) for index, tup in enumerate(onset_pitch_slice): onset_pitch_dict[tup].append(index) duplicates = {key: indices for key, indices in onset_pitch_dict.items() if len(indices) > 1} - - voice_overlap_note_ids = [spart_array[idx][:,-1] for k, idx in duplicates.items()] - voice_overlap_note_ids = [i for sub in voice_overlap_note_ids for i in sub] + if len(duplicates) > 0: + duplicate_idx = np.array(list(duplicates.values())).flatten() + voice_overlap_note_ids = list(spart[duplicate_idx]['id']) for al_note in alignment: label = al_note["label"] diff --git a/partitura/io/matchfile_utils.py b/partitura/io/matchfile_utils.py index 3f07cc2d..b2dbf6b6 100644 --- a/partitura/io/matchfile_utils.py +++ b/partitura/io/matchfile_utils.py @@ -888,7 +888,7 @@ def __str__(self): def from_string(cls, string: str) -> MatchTempoIndication: - # Note particularities of the BpM dataset.... + # NOTE particularities of the BpM dataset.... if string is not None: if 'Rond' in string: content = string.split(' ') @@ -897,9 +897,11 @@ def from_string(cls, string: str) -> MatchTempoIndication: content = ['Allegretto'] elif 'Menuetto' in string: content = ['Menuetto'] + else: content = interpret_as_list(string) else: content = interpret_as_list(string) + print(f'{string} -> {content}') return content diff --git a/tests/__init__.py b/tests/__init__.py index 6d08b32c..c96ad458 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -24,7 +24,11 @@ # yield identical MusicXML MUSICXML_IMPORT_EXPORT_TESTFILES = [ os.path.join(MUSICXML_PATH, fn) - for fn in ["test_note_ties.xml", "test_note_ties_divs.xml", "test_score_object.musicxml"] + for fn in ["test_note_ties.xml", "test_note_ties_divs.xml"] +] +MUSICXML_SCORE_OBJECT_TESTFILES = [ + os.path.join(MUSICXML_PATH, fn) + for fn in ["test_score_object.musicxml"] ] MUSICXML_UNFOLD_TESTPAIRS = [ ( diff --git a/tests/test_pianoroll.py b/tests/test_pianoroll.py index 404a900e..7e04e366 100644 --- a/tests/test_pianoroll.py +++ b/tests/test_pianoroll.py @@ -214,7 +214,7 @@ def test_pianoroll_to_notearray(self): self.assertTrue(test) def test_reconstruction_score(self): - for fn in MUSICXML_IMPORT_EXPORT_TESTFILES[:2]: + for fn in MUSICXML_IMPORT_EXPORT_TESTFILES: score = load_musicxml(fn) note_array = score[0].note_array() pr = compute_pianoroll( diff --git a/tests/test_xml.py b/tests/test_xml.py index 02438f35..05959d3f 100755 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -10,6 +10,7 @@ from tests import ( MUSICXML_IMPORT_EXPORT_TESTFILES, + MUSICXML_SCORE_OBJECT_TESTFILES, MUSICXML_UNFOLD_TESTPAIRS, MUSICXML_UNFOLD_COMPLEX, MUSICXML_UNFOLD_VOLTA, @@ -55,7 +56,7 @@ class TestMusicXML(unittest.TestCase): """ def test_import_export(self): - for fn in MUSICXML_IMPORT_EXPORT_TESTFILES[:2]: + for fn in MUSICXML_IMPORT_EXPORT_TESTFILES: with open(fn) as f: parts = load_musicxml(f, validate=False) result = save_musicxml(parts).decode("UTF-8") @@ -243,7 +244,7 @@ def _pretty_export_import_pretty_test(self, part1): self.assertTrue(equal, msg) def test_score_attribute(self): - score = load_musicxml(MUSICXML_IMPORT_EXPORT_TESTFILES[2]) + score = load_musicxml(MUSICXML_SCORE_OBJECT_TESTFILES[0]) test_work_title = "Test Title" test_work_number = "Test Opus 1" From 98df8beba66ff8e1b8e85fbf9898a2dadaed0c9d Mon Sep 17 00:00:00 2001 From: huispaty Date: Tue, 8 Aug 2023 18:00:13 +0200 Subject: [PATCH 28/80] del print statements --- partitura/io/exportmatch.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index 84abe403..0801c6b2 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -401,9 +401,6 @@ def matchfile_from_alignment( note_lines = [] # Get ids of notes which voice overlap - print('12987329412398476') - print(type(spart)) - print(type(spart.note_array())) onset_pitch_slice = [tuple(i) for i in list(spart.note_array()[["onset_beat", "pitch"]])] onset_pitch_dict = defaultdict(list) From 66ca02a599712fe2b2fe717775646e8f57b11715 Mon Sep 17 00:00:00 2001 From: huispaty Date: Thu, 10 Aug 2023 16:12:20 +0200 Subject: [PATCH 29/80] more efficient voice overlap note identification --- partitura/io/exportmatch.py | 21 ++++++++++++--------- partitura/io/matchfile_utils.py | 4 +--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index 0801c6b2..193b080f 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -399,17 +399,20 @@ def matchfile_from_alignment( sort_stime = [] note_lines = [] - - # Get ids of notes which voice overlap - onset_pitch_slice = [tuple(i) for i in list(spart.note_array()[["onset_beat", "pitch"]])] - onset_pitch_dict = defaultdict(list) - for index, tup in enumerate(onset_pitch_slice): - onset_pitch_dict[tup].append(index) - duplicates = {key: indices for key, indices in onset_pitch_dict.items() if len(indices) > 1} + # Get ids of notes which voice overlap + sna = spart.note_array() + onset_pitch_slice = sna[["onset_div", "pitch"]] + uniques, counts = np.unique(onset_pitch_slice, return_counts=True) + duplicate_values = uniques[counts > 1] + duplicates = dict() + for v in duplicate_values: + idx = np.where(onset_pitch_slice == v)[0] + duplicates[tuple(v)] = idx + voice_overlap_note_ids = [] if len(duplicates) > 0: - duplicate_idx = np.array(list(duplicates.values())).flatten() - voice_overlap_note_ids = list(spart[duplicate_idx]['id']) + duplicate_idx = np.concatenate(np.array(list(duplicates.values()))).flatten() + voice_overlap_note_ids = list(sna[duplicate_idx]['id']) for al_note in alignment: label = al_note["label"] diff --git a/partitura/io/matchfile_utils.py b/partitura/io/matchfile_utils.py index b2dbf6b6..864d6372 100644 --- a/partitura/io/matchfile_utils.py +++ b/partitura/io/matchfile_utils.py @@ -899,9 +899,7 @@ def from_string(cls, string: str) -> MatchTempoIndication: content = ['Menuetto'] else: content = interpret_as_list(string) else: - content = interpret_as_list(string) - - print(f'{string} -> {content}') + content = interpret_as_list(string) return content From c115949f7398e5e485aaba5633647c5be3633a38 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Thu, 10 Aug 2023 16:25:31 +0200 Subject: [PATCH 30/80] Fixed ppart_from_matchfile when first_note_at_zero: Fixed bug when the first note in matchfile is not the first performed note in time. --- partitura/io/importmatch.py | 53 ++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index 04e96f8f..a7202ebd 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -371,29 +371,38 @@ def performed_part_from_match( # PerformedNote instances for all MatchNotes notes = [] - first_note = next(mf.iter_notes(), None) - if first_note and first_note_at_zero: - offset = midi_ticks_to_seconds(first_note.Onset, mpq=mpq, ppq=ppq) - offset_tick = first_note.Onset - else: - offset = 0 - offset_tick = 0 - - notes = [ - dict( - id=format_pnote_id(note.Id), - midi_pitch=note.MidiPitch, - note_on=midi_ticks_to_seconds(note.Onset, mpq, ppq) - offset, - note_off=midi_ticks_to_seconds(note.Offset, mpq, ppq) - offset, - note_on_tick=note.Onset - offset_tick, - note_off_tick=note.Offset - offset_tick, - sound_off=midi_ticks_to_seconds(note.Offset, mpq, ppq) - offset, - velocity=note.Velocity, - track=getattr(note, "Track", 0), - channel=getattr(note, "Channel", 1), + notes = list() + note_onsets_in_secs = np.array(np.zeros(len(mf.notes)), dtype=float) + note_onsets_in_tick = np.array(np.zeros(len(mf.notes)), dtype=int) + for i, note in enumerate(mf.notes): + n_onset_sec = midi_ticks_to_seconds(note.Onset, mpq, ppq) + note_onsets_in_secs[i] = n_onset_sec + note_onsets_in_tick[i] = note.Onset + notes.append( + dict( + id=format_pnote_id(note.Id), + midi_pitch=note.MidiPitch, + note_on=n_onset_sec, + note_off=midi_ticks_to_seconds(note.Offset, mpq, ppq), + note_on_tick=note.Onset, + note_off_tick=note.Offset, + sound_off=midi_ticks_to_seconds(note.Offset, mpq, ppq), + velocity=note.Velocity, + track=getattr(note, "Track", 0), + channel=getattr(note, "Channel", 1), + ) ) - for note in mf.notes - ] + # Set first note_on to zero in ticks and seconds if first_note_at_zero + if first_note_at_zero and len(note_onsets_in_secs) > 0: + offset = note_onsets_in_secs.min() + offset_tick = note_onsets_in_tick.min() + if offset > 0 and offset_tick > 0: + for note in notes: + note["note_on"] -= offset + note["note_off"] -= offset + note["sound_off"] -= offset + note["note_on_tick"] -= offset_tick + note["note_off_tick"] -= offset_tick # SustainPedal instances for sustain pedal lines sustain_pedal = [ From d09156374311bd59e374a6da14341471ecf1093b Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Thu, 10 Aug 2023 16:47:23 +0200 Subject: [PATCH 31/80] fixes for time_slice from ppart. Changed initialization of ppart towards the end to avoid errors with Performance Notes. --- partitura/utils/music.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index a1469e50..743db632 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3252,18 +3252,17 @@ def slice_ppart_by_time( # create a new (empty) instance of a PerformedPart # single dummy note added to be able to set sustain_pedal_threshold in __init__ # -> check `adjust_offsets_w_sustain` in partitura.performance - ppart_slice = PerformedPart([{"note_on": 0, "note_off": 0}]) + # ppart_slice = PerformedPart([{"note_on": 0, "note_off": 0, "pitch": 0}]) # get ppq if PerformedPart contains it, # else skip time_tick info when e.g. created with 'load_performance_midi' try: ppq = ppart.ppq - ppart_slice.ppq = ppq except AttributeError: ppq = None + controls_slice = [] if ppart.controls: - controls_slice = [] for cc in ppart.controls: if cc["time"] >= start_time and cc["time"] <= end_time: new_cc = cc.copy() @@ -3271,10 +3270,9 @@ def slice_ppart_by_time( if ppq: new_cc["time_tick"] = int(2 * ppq * cc["time"]) controls_slice.append(new_cc) - ppart_slice.controls = controls_slice + programs_slice = [] if ppart.programs: - programs_slice = [] for pr in ppart.programs: if pr["time"] >= start_time and pr["time"] <= end_time: new_pr = pr.copy() @@ -3282,7 +3280,7 @@ def slice_ppart_by_time( if ppq: new_pr["time_tick"] = int(2 * ppq * pr["time"]) programs_slice.append(new_pr) - ppart_slice.programs = programs_slice + notes_slice = [] note_id = 0 @@ -3326,7 +3324,8 @@ def slice_ppart_by_time( else: break - ppart_slice.notes = notes_slice + # Create slice PerformedPart + ppart_slice = PerformedPart(notes=notes_slice, programs=programs_slice, controls=controls_slice, ppq=ppq) # set threshold property after creating notes list to update 'sound_offset' values ppart_slice.sustain_pedal_threshold = ppart.sustain_pedal_threshold From 76381eba92cf1d99cfb62ea3da9116efed6e27d0 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Thu, 10 Aug 2023 18:15:31 +0200 Subject: [PATCH 32/80] Addresses #306 This PR presents a solution for faster checking of empty and duplicate lines in matchfiles using numpy arrays and numpy vectorization. --- partitura/io/importmatch.py | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index 04e96f8f..14f69828 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -6,7 +6,7 @@ import os from typing import Union, Tuple, Optional, Callable, List import warnings - +from functools import partial import numpy as np from partitura import score @@ -197,21 +197,31 @@ def load_matchfile( parsed_lines = list() # Functionality to remove duplicate lines - for i, line in enumerate(raw_lines): - if line in raw_lines[i + 1 :] and line != "": - warnings.warn(f"Duplicate line found in matchfile: {line}") - continue - parsed_line = parse_matchline(line, from_matchline_methods, version) - if parsed_line is None: - warnings.warn(f"Could not empty parse line: {line} ") - continue - parsed_lines.append(parsed_line) - + len_raw_lines = len(raw_lines) + np_lines = np.array(raw_lines, dtype=str) + # Remove empty lines + np_lines = np_lines[np_lines != ""] + len_after_empty_lines = len(np_lines) + if len_raw_lines != len_after_empty_lines: + warnings.warn( + f"Removed {len_raw_lines - len_after_empty_lines} empty lines from match file" + ) + # Remove duplicate lines + _, idx = np.unique(np_lines, return_index=True) + np_lines = np_lines[np.sort(idx)] + len_after_duplicate_lines = len(np_lines) + if len_after_empty_lines != len_after_duplicate_lines: + warnings.warn( + f"Removed {len_after_empty_lines - len_after_duplicate_lines} duplicate lines from match file" + ) + # Parse lines + f = partial(parse_matchline, version=version, from_matchline_methods=from_matchline_methods) + f_vec = np.vectorize(f) + parsed_lines = f_vec(np_lines).tolist() + # Create MatchFile instance mf = MatchFile(lines=parsed_lines) - # Validate match for duplicate snote_ids or pnote_ids validate_match_ids(mf) - return mf From 5f18fd806841e2cb2e68efc6f5b8ee816acf30f3 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Fri, 18 Aug 2023 11:30:47 +0200 Subject: [PATCH 33/80] add a bugfix for midi scores that only report time signature in one track. Now this ts is shared between all the tracks --- partitura/io/importmidi.py | 12 ++++++++++++ tests/__init__.py | 4 ++++ tests/data/midi/bach_midi_score.mid | Bin 0 -> 5200 bytes tests/test_midi_import.py | 9 +++++++++ 4 files changed, 25 insertions(+) create mode 100644 tests/data/midi/bach_midi_score.mid diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index ecfe2be3..ff5421b6 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -473,6 +473,18 @@ def load_score_midi( else: note_ids = [None for i in range(len(note_array))] + ## sanitize time signature, when they are only present in one track, and no global is set + # find the number of ts per each track + number_of_time_sig_per_track = [len(time_sigs_by_track[t]) for t in key_sigs_by_track.keys()] + # if one track has 0 ts, and another has !=0 ts, and no global_time_sigs is present, sanitize + # all key signatures are copied to global, and the track ts are removed + if len(global_time_sigs) == 0 and min(number_of_time_sig_per_track) == 0 and max(number_of_time_sig_per_track)!= 0: + warnings.warn("Sanitizing time signatures. They will be shared across all tracks.") + for ts in [ts for ts_track in time_sigs_by_track.values() for ts in ts_track]: #flattening all track time signatures to a list of ts + global_time_sigs.append(ts) + # now clear all track_ts + time_sigs_by_track.clear() + time_sigs_by_part = defaultdict(set) for tr, ts_list in time_sigs_by_track.items(): for ts in ts_list: diff --git a/tests/__init__.py b/tests/__init__.py index ce7bf09c..80e898ab 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -236,4 +236,8 @@ MIDIEXPORT_TESTFILES = [ os.path.join(DATA_PATH, "musicxml", "test_anacrusis.xml") +] + +MIDIINPORT_TESTFILES = [ + os.path.join(DATA_PATH, "midi", "bach_midi_score.mid") ] \ No newline at end of file diff --git a/tests/data/midi/bach_midi_score.mid b/tests/data/midi/bach_midi_score.mid new file mode 100644 index 0000000000000000000000000000000000000000..d2f945a4b586b543ca93030ba2d25c08ee5fc32f GIT binary patch literal 5200 zcmeHKOKuxS5UsX?DDerhO!fjKS|Uw-DblnoL8e%gwMY;IgaI!ic%dMh2A2{cWScAa z20ns!xkhfHyy;gQ*O=W3H`!$~tbSep-wa1rzrGWZuO-Nn@b~EI_xB=C-^;^~U#+cu z_l-RK^kiM+VY0UV;d}Ww6Zs_aas8b5#(eHKAz>=Ee|P?0h~;}CeLj^ zFW@UYw?VVhD{#K5qRQNvoK>#YY4KM}@$rcNUzW{btd=DQGi7Gxo<*D*94Dpx>!_iJ zJ+jPS!Z{Jk{3V>dnU?L$J*{UQ!dsif6!pwK{{>G`TU?}*KeIFwGtLB;@ZT5E zX<3RIQYX&_8V6!=5lOPN8@O$#hdN(te4U#D;^bMDq6Ws5IA^3^YJ4f?1Ff?z#~P0% z!_(8)lW?H%KtfkzS7z>6#1Azd=J-o^%1K6l3GZv{=k%BG7aCtk*weTtGxrPy{O-D= zu_Iwuf7%3@&dIcMBYtOIE`fA-g8LFINLH0$4BKUym zCFm4TFG2XiIW(^O@!^8&VN>|Y`R!kePtEi_*}}?gRT8&1D8x2S+x4l9Q@cKOa6+iY z*}OBk1870{Yn}5cbh0H$4jyv zrw=|N(!?2~zoiwAdt_hZzBQZ0&TeXJMA|K`L>hQ!cJXOrdu^a)dHb;XL3il zJ90YbQ*f>l4Sit*K1ybVc!_3q$y1^^xa29(C0UQt2Okk>;=W40J|3qJX}yIbjmQ>n zNfReRyF^HHJ&>kFuYsDqHX>HAZPW(c-$NBsQPL#ZDA}~Nz>=oaCMu2D9>BeYm(=LH zi}r}D=1sN0$;lXeJU+9J+seAU4_{NSCztKC32;-wJ_7%I*9C}XrVY+ROCy3O?QzZz z@e)nH$y1^kEqO|GN!H`b^Gt7v``VDEMUO8do^!Dms$E&{*#Ba0C!HM>0{gy=pR|oq zlUjA`??=6VQpvJlAL4ieTT_^L7Bpqn_{Nz9v*KCs7}k%!w4McV|b?jHl>yfFX( literal 0 HcmV?d00001 diff --git a/tests/test_midi_import.py b/tests/test_midi_import.py index cf31f169..0255d38e 100644 --- a/tests/test_midi_import.py +++ b/tests/test_midi_import.py @@ -13,6 +13,7 @@ from partitura import load_score_midi from partitura.utils import partition import partitura.score as score +from tests import MIDIINPORT_TESTFILES LOGGER = logging.getLogger(__name__) @@ -310,3 +311,11 @@ def test_midi_import_mode_5(self): def tearDown(self): # remove tmp file self.tmpfile = None + +class TestScoreMidi(unittest.TestCase): + def test_time_signature(self): + score = load_score_midi(MIDIINPORT_TESTFILES[0]) + self.assertEqual(score.note_array()["onset_beat"][2], 0.5) + na = score.note_array(include_time_signature=True) + self.assertTrue(all([n==3 for n in na["ts_beats"]])) + self.assertTrue(all([d==8 for d in na["ts_beat_type"]])) \ No newline at end of file From 936e673dff34cd0d64d259309ce0af86568c8be2 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Wed, 23 Aug 2023 19:10:10 +0200 Subject: [PATCH 34/80] mei measure number is now an integer, and. Measure.name is the string saved in the score --- partitura/io/importmei.py | 11 ++++++++--- tests/test_mei.py | 9 +++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index eb8bcaa3..08d0e825 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -940,7 +940,7 @@ def _handle_layer_in_staff_in_measure( position = new_position return position - def _handle_staff_in_measure(self, staff_el, staff_ind, position: int, part): + def _handle_staff_in_measure(self, staff_el, staff_ind, position: int, part: pt.score.Part, measure_number: int): """ Handles staffs inside a measure element. @@ -954,6 +954,9 @@ def _handle_staff_in_measure(self, staff_el, staff_ind, position: int, part): The current position on the timeline. part : Partitura.Part The created partitura part object. + measure_number : int + The number of the measure. This number is independent of the measure name specified in the score. + It starts from 0 and always increases by 1 at each measure Returns ------- @@ -961,7 +964,7 @@ def _handle_staff_in_measure(self, staff_el, staff_ind, position: int, part): The final position on the timeline. """ # add measure - measure = score.Measure(number=staff_el.getparent().get("n")) + measure = score.Measure(number=measure_number,name=staff_el.getparent().get("n")) part.add(measure, position) layers_el = staff_el.findall(self._ns_name("layer")) @@ -1033,6 +1036,7 @@ def _handle_section(self, section_el, parts, position: int): position : int The end position of the section. """ + measure_number = 0 for i_el, element in enumerate(section_el): # handle measures if element.tag == self._ns_name("measure"): @@ -1045,7 +1049,7 @@ def _handle_section(self, section_el, parts, position: int): end_positions = [] for i_s, (part, staff_el) in enumerate(zip(parts, staves_el)): end_positions.append( - self._handle_staff_in_measure(staff_el, i_s + 1, position, part) + self._handle_staff_in_measure(staff_el, i_s + 1, position, part, measure_number) ) # handle directives (dir elements) self._handle_directives(element, position) @@ -1067,6 +1071,7 @@ def _handle_section(self, section_el, parts, position: int): position = max_position # handle right barline symbol self._handle_barline_symbols(element, position, "right") + measure_number += 1 # handle staffDef elements elif element.tag == self._ns_name("scoreDef"): # meter modifications diff --git a/tests/test_mei.py b/tests/test_mei.py index d580b551..0afd9ec6 100644 --- a/tests/test_mei.py +++ b/tests/test_mei.py @@ -285,5 +285,14 @@ def test_tuplet_div(self): score = load_mei(MEI_TESTFILES[17]) self.assertTrue(np.array_equal(score.note_array()["duration_div"],[3,3,3,3,3,3,3,3,24])) + def test_measure_number(self): + score = load_mei(MEI_TESTFILES[0]) + measure_number_map = score.parts[0].measure_number_map + onsets = score.note_array()["onset_div"] + measure_number_per_each_onset = measure_number_map(onsets) + self.assertTrue(measure_number_per_each_onset[0].dtype == int) + self.assertTrue(min(measure_number_per_each_onset) == 0) + self.assertTrue(max(measure_number_per_each_onset) == 33) + if __name__ == "__main__": unittest.main() From cc28af021a03c5560e11cbe7a48781fc651aab84 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Tue, 29 Aug 2023 11:34:34 +0200 Subject: [PATCH 35/80] measure number starts from 1 to behave like musicxml import --- partitura/io/importmei.py | 4 ++-- tests/test_mei.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 08d0e825..1c48a9df 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -956,7 +956,7 @@ def _handle_staff_in_measure(self, staff_el, staff_ind, position: int, part: pt. The created partitura part object. measure_number : int The number of the measure. This number is independent of the measure name specified in the score. - It starts from 0 and always increases by 1 at each measure + It starts from 1 and always increases by 1 at each measure Returns ------- @@ -1036,7 +1036,7 @@ def _handle_section(self, section_el, parts, position: int): position : int The end position of the section. """ - measure_number = 0 + measure_number = 1 for i_el, element in enumerate(section_el): # handle measures if element.tag == self._ns_name("measure"): diff --git a/tests/test_mei.py b/tests/test_mei.py index 0afd9ec6..a1abdc64 100644 --- a/tests/test_mei.py +++ b/tests/test_mei.py @@ -291,8 +291,8 @@ def test_measure_number(self): onsets = score.note_array()["onset_div"] measure_number_per_each_onset = measure_number_map(onsets) self.assertTrue(measure_number_per_each_onset[0].dtype == int) - self.assertTrue(min(measure_number_per_each_onset) == 0) - self.assertTrue(max(measure_number_per_each_onset) == 33) + self.assertTrue(min(measure_number_per_each_onset) == 1) + self.assertTrue(max(measure_number_per_each_onset) == 34) if __name__ == "__main__": unittest.main() From 9cb7eba753fdc6439e40a755887ccdc9eaf2df8a Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Thu, 31 Aug 2023 15:53:36 +0200 Subject: [PATCH 36/80] Solved a bug which was resetting the measure counter in case of volta measures --- partitura/io/importmei.py | 18 +++++++++++------- tests/test_mei.py | 6 ++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index 1c48a9df..bfdc6ab6 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -95,10 +95,11 @@ def fill_parts(self): raise Exception("Only MEI with a single score element are supported") sections_el = scores_el[0].findall(self._ns_name("section")) position = 0 + measure_number = 1 for section_el in sections_el: # insert in parts all elements except ties - position = self._handle_section( - section_el, list(score.iter_parts(self.parts)), position + position, measure_number = self._handle_section( + section_el, list(score.iter_parts(self.parts)), position, measure_number ) # handles ties @@ -1018,7 +1019,7 @@ def _handle_directives(self, measure_el, position): for dir_el in dir_els: self._handle_dir_element(dir_el, position) - def _handle_section(self, section_el, parts, position: int): + def _handle_section(self, section_el, parts, position: int, measure_number: int): """ Returns position and fills parts with elements. @@ -1030,13 +1031,16 @@ def _handle_section(self, section_el, parts, position: int): A list of partitura Parts. position : int The current position on the timeline. + measure_number : int + The current measure_number Returns ------- position : int The end position of the section. + measure_number : int + The number of the last measure. """ - measure_number = 1 for i_el, element in enumerate(section_el): # handle measures if element.tag == self._ns_name("measure"): @@ -1088,10 +1092,10 @@ def _handle_section(self, section_el, parts, position: int): self._handle_keysig(element, position, part) # handle nested section elif element.tag == self._ns_name("section"): - position = self._handle_section(element, parts, position) + position, measure_number = self._handle_section(element, parts, position, measure_number) elif element.tag == self._ns_name("ending"): ending_start = position - position = self._handle_section(element, parts, position) + position, measure_number = self._handle_section(element, parts, position, measure_number) # insert the ending element ending_number = int(re.sub("[^0-9]", "", element.attrib["n"])) self._add_ending(ending_start, position, ending_number, parts) @@ -1107,7 +1111,7 @@ def _handle_section(self, section_el, parts, position: int): else: raise Exception(f"element {element.tag} is not yet supported") - return position + return position, measure_number def _add_ending(self, start_ending, end_ending, ending_string, parts): for part in score.iter_parts(parts): diff --git a/tests/test_mei.py b/tests/test_mei.py index a1abdc64..782c7976 100644 --- a/tests/test_mei.py +++ b/tests/test_mei.py @@ -294,5 +294,11 @@ def test_measure_number(self): self.assertTrue(min(measure_number_per_each_onset) == 1) self.assertTrue(max(measure_number_per_each_onset) == 34) + def test_measure_number2(self): + score = load_mei(MEI_TESTFILES[13]) + measure_number_map = score.parts[0].measure_number_map + measure_number_per_each_onset = measure_number_map(score.note_array()["onset_div"]) + self.assertTrue(measure_number_per_each_onset.tolist()==[1,2,2,3,4,5,6,8]) + if __name__ == "__main__": unittest.main() From 3080de3bb116ba33851fc839b950455fa566a3bd Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Thu, 31 Aug 2023 15:17:34 +0000 Subject: [PATCH 37/80] Format code with black (bot) --- partitura/io/importmei.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/partitura/io/importmei.py b/partitura/io/importmei.py index bfdc6ab6..2f4ba704 100644 --- a/partitura/io/importmei.py +++ b/partitura/io/importmei.py @@ -941,7 +941,14 @@ def _handle_layer_in_staff_in_measure( position = new_position return position - def _handle_staff_in_measure(self, staff_el, staff_ind, position: int, part: pt.score.Part, measure_number: int): + def _handle_staff_in_measure( + self, + staff_el, + staff_ind, + position: int, + part: pt.score.Part, + measure_number: int, + ): """ Handles staffs inside a measure element. @@ -965,7 +972,9 @@ def _handle_staff_in_measure(self, staff_el, staff_ind, position: int, part: pt. The final position on the timeline. """ # add measure - measure = score.Measure(number=measure_number,name=staff_el.getparent().get("n")) + measure = score.Measure( + number=measure_number, name=staff_el.getparent().get("n") + ) part.add(measure, position) layers_el = staff_el.findall(self._ns_name("layer")) @@ -1032,14 +1041,14 @@ def _handle_section(self, section_el, parts, position: int, measure_number: int) position : int The current position on the timeline. measure_number : int - The current measure_number + The current measure_number Returns ------- position : int The end position of the section. measure_number : int - The number of the last measure. + The number of the last measure. """ for i_el, element in enumerate(section_el): # handle measures @@ -1053,7 +1062,9 @@ def _handle_section(self, section_el, parts, position: int, measure_number: int) end_positions = [] for i_s, (part, staff_el) in enumerate(zip(parts, staves_el)): end_positions.append( - self._handle_staff_in_measure(staff_el, i_s + 1, position, part, measure_number) + self._handle_staff_in_measure( + staff_el, i_s + 1, position, part, measure_number + ) ) # handle directives (dir elements) self._handle_directives(element, position) @@ -1092,10 +1103,14 @@ def _handle_section(self, section_el, parts, position: int, measure_number: int) self._handle_keysig(element, position, part) # handle nested section elif element.tag == self._ns_name("section"): - position, measure_number = self._handle_section(element, parts, position, measure_number) + position, measure_number = self._handle_section( + element, parts, position, measure_number + ) elif element.tag == self._ns_name("ending"): ending_start = position - position, measure_number = self._handle_section(element, parts, position, measure_number) + position, measure_number = self._handle_section( + element, parts, position, measure_number + ) # insert the ending element ending_number = int(re.sub("[^0-9]", "", element.attrib["n"])) self._add_ending(ending_start, position, ending_number, parts) From c10bfab055a3236986dab77400ec2c5ffad57df4 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Fri, 8 Sep 2023 12:55:36 +0200 Subject: [PATCH 38/80] fixed bug in case of match files load_score works based on the extension --- partitura/io/__init__.py | 52 +++++++++++++--------------------------- 1 file changed, 16 insertions(+), 36 deletions(-) diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index c142a681..be42804e 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -35,7 +35,7 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: """ Load a score format supported by partitura. Currently the accepted formats are MusicXML, MIDI, Kern and MEI, plus all formats for which - MuseScore has support import-support (requires MuseScore 3). + MuseScore has support import-support (requires MuseScore 4 or 3). Parameters ---------- @@ -54,20 +54,15 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: scr: :class:`partitura.score.Score` A score instance. """ - part = None - - # Catch exceptions - exception_dictionary = dict() - # Load MusicXML - try: + extension = filename.split(".")[-1].lower() + if extension in ("mxl", "xml", "musicxml"): + # Load MusicXML return load_musicxml( filename=filename, force_note_ids=force_note_ids, ) - except Exception as e: - exception_dictionary["MusicXML"] = e - # Load MIDI - try: + elif extension in ["midi", "mid"]: + # Load MIDI if (force_note_ids is None) or (not force_note_ids): assign_note_ids = False else: @@ -76,44 +71,29 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: filename=filename, assign_note_ids=assign_note_ids, ) - except Exception as e: - exception_dictionary["MIDI"] = e - # Load MEI - try: + elif extension in ["mei"]: + # Load MEI return load_mei(filename=filename) - except Exception as e: - exception_dictionary["MEI"] = e - # Load Kern - try: + elif extension in ["kern", "krn"]: return load_kern( filename=filename, force_note_ids=force_note_ids, ) - except Exception as e: - exception_dictionary["Kern"] = e - # Load MuseScore - try: + elif extension in ["mscz", "mscx", "musescore", "mscore", "ms"]: + # Load MuseScore return load_via_musescore( filename=filename, force_note_ids=force_note_ids, ) - except Exception as e: - exception_dictionary["MuseScore"] = e - try: + elif extension in ["match"]: # Load the score information from a Matchfile - _, _, part = load_match( + _, _, score = load_match( filename=filename, create_score=True, ) - - except Exception as e: - exception_dictionary["matchfile"] = e - if part is None: - for score_format, exception in exception_dictionary.items(): - print(f"Error loading score as {score_format}:") - print(exception) - - raise NotSupportedFormatError + return score + else: + raise NotSupportedFormatError(f"{filename.split('.')[-1].lower()} file extension is not supported. If this should be supported, consider editing partitura/io/__init__.py file") def load_score_as_part(filename: PathLike) -> Part: From 9ce24be24d16b3b1a67370ccfcd17ceccc3bf1f0 Mon Sep 17 00:00:00 2001 From: fosfrancesco Date: Tue, 12 Sep 2023 16:39:53 +0200 Subject: [PATCH 39/80] use os function to get the file extension --- partitura/io/__init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index be42804e..2e67cef2 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -4,6 +4,7 @@ This module contains methods for importing and exporting symbolic music formats. """ from typing import Union +import os from .importmusicxml import load_musicxml from .importmidi import load_score_midi, load_performance_midi @@ -54,14 +55,15 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: scr: :class:`partitura.score.Score` A score instance. """ - extension = filename.split(".")[-1].lower() - if extension in ("mxl", "xml", "musicxml"): + + extension = os.path.splitext(filename)[-1].lower() + if extension in (".mxl", ".xml", ".musicxml"): # Load MusicXML return load_musicxml( filename=filename, force_note_ids=force_note_ids, ) - elif extension in ["midi", "mid"]: + elif extension in [".midi", ".mid"]: # Load MIDI if (force_note_ids is None) or (not force_note_ids): assign_note_ids = False @@ -71,21 +73,21 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: filename=filename, assign_note_ids=assign_note_ids, ) - elif extension in ["mei"]: + elif extension in [".mei"]: # Load MEI return load_mei(filename=filename) - elif extension in ["kern", "krn"]: + elif extension in [".kern", ".krn"]: return load_kern( filename=filename, force_note_ids=force_note_ids, ) - elif extension in ["mscz", "mscx", "musescore", "mscore", "ms"]: + elif extension in [".mscz", ".mscx", ".musescore", ".mscore", ".ms"]: # Load MuseScore return load_via_musescore( filename=filename, force_note_ids=force_note_ids, ) - elif extension in ["match"]: + elif extension in [".match"]: # Load the score information from a Matchfile _, _, score = load_match( filename=filename, @@ -93,7 +95,7 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: ) return score else: - raise NotSupportedFormatError(f"{filename.split('.')[-1].lower()} file extension is not supported. If this should be supported, consider editing partitura/io/__init__.py file") + raise NotSupportedFormatError(f"{extension} file extension is not supported. If this should be supported, consider editing partitura/io/__init__.py file") def load_score_as_part(filename: PathLike) -> Part: From dd5b2ff75d46a27e7fa27706446852f9adee1de3 Mon Sep 17 00:00:00 2001 From: manoskary Date: Wed, 13 Sep 2023 10:10:55 +0000 Subject: [PATCH 40/80] Format code with black (bot) --- partitura/io/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index 2e67cef2..2228321a 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -55,7 +55,7 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: scr: :class:`partitura.score.Score` A score instance. """ - + extension = os.path.splitext(filename)[-1].lower() if extension in (".mxl", ".xml", ".musicxml"): # Load MusicXML @@ -95,7 +95,9 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: ) return score else: - raise NotSupportedFormatError(f"{extension} file extension is not supported. If this should be supported, consider editing partitura/io/__init__.py file") + raise NotSupportedFormatError( + f"{extension} file extension is not supported. If this should be supported, consider editing partitura/io/__init__.py file" + ) def load_score_as_part(filename: PathLike) -> Part: From aaf292e35af562b091656d5f49453713ce28acbd Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Thu, 14 Sep 2023 17:58:53 +0200 Subject: [PATCH 41/80] solution for empty dynamic directions. --- partitura/io/importmusicxml.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index c7fc2bb8..042bfba4 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -841,6 +841,9 @@ def _handle_direction(e, position, part, ongoing): # first child of direction-type is dynamics, there may be subsequent # dynamics items, so we loop: for child in direction_type: + # check if child has no children, in which case continue + if len(child) == 0: + continue # interpret as score.Direction, fall back to score.Words dyn_el = next(iter(child)) if dyn_el is not None: From 19af14ca0651fffe535c5958616e6a4213812cea Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 15 Sep 2023 15:47:58 +0200 Subject: [PATCH 42/80] added empty dynamics direction to adress issue #315 and its solution. --- tests/data/musicxml/test_note_features.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/data/musicxml/test_note_features.xml b/tests/data/musicxml/test_note_features.xml index 343d951f..371ed54d 100644 --- a/tests/data/musicxml/test_note_features.xml +++ b/tests/data/musicxml/test_note_features.xml @@ -84,6 +84,14 @@ 2 + + + + + + 1 + + G From 32cf67079ef7dc66687ad9b528d9dcd06697f903 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Fri, 15 Sep 2023 15:49:55 +0200 Subject: [PATCH 43/80] adapted code to proposed solution, if iterable has no next element default to None. --- partitura/io/importmusicxml.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index 042bfba4..c6d3e0b7 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -842,10 +842,8 @@ def _handle_direction(e, position, part, ongoing): # dynamics items, so we loop: for child in direction_type: # check if child has no children, in which case continue - if len(child) == 0: - continue # interpret as score.Direction, fall back to score.Words - dyn_el = next(iter(child)) + dyn_el = next(iter(child), None) if dyn_el is not None: direction = DYN_DIRECTIONS.get(dyn_el.tag, score.Words)( dyn_el.tag, staff=staff From baa2feedb3dfe7f2d734e7cd3616b0a16dcb8e50 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 20 Sep 2023 10:54:47 +0200 Subject: [PATCH 44/80] A unit test example for cross staff beaming. The possibility of testing other formats is left open. --- .../test_cross_staff_beaming.musicxml | 390 ++++++++++++++++++ tests/test_cross_staff_beaming.py | 20 + 2 files changed, 410 insertions(+) create mode 100644 tests/data/musicxml/test_cross_staff_beaming.musicxml create mode 100644 tests/test_cross_staff_beaming.py diff --git a/tests/data/musicxml/test_cross_staff_beaming.musicxml b/tests/data/musicxml/test_cross_staff_beaming.musicxml new file mode 100644 index 00000000..f30dcddc --- /dev/null +++ b/tests/data/musicxml/test_cross_staff_beaming.musicxml @@ -0,0 +1,390 @@ + + + + + Untitled score + + + Composer / arranger + + MuseScore 4.0.0 + 2023-09-20 + + + + + + + + + + Piano + Pno. + + Piano + + + + 1 + 1 + 78.7402 + 0 + + + + + + + 2 + + 0 + + + 2 + + G + 2 + + + F + 4 + + + + + G + 4 + + 1 + 1 + eighth + down + 1 + begin + + + + F + 4 + + 1 + 1 + eighth + down + 1 + continue + + + + E + 4 + + 1 + 1 + eighth + down + 1 + continue + + + + D + 4 + + 1 + 1 + eighth + down + 1 + end + + + + C + 4 + + 1 + 1 + eighth + down + 1 + begin + + + + B + 3 + + 1 + 1 + eighth + up + 2 + continue + + + + A + 3 + + 1 + 1 + eighth + up + 2 + continue + + + + G + 3 + + 1 + 1 + eighth + up + 2 + end + + + 8 + + + + C + 5 + + 4 + 2 + half + up + 1 + + + + D + 5 + + 4 + 2 + half + up + 1 + + + 8 + + + + C + 3 + + 4 + 5 + half + down + 2 + + + + + E + 3 + + 4 + 5 + half + down + 2 + + + + B + 2 + + 4 + 5 + half + down + 2 + + + + + D + 3 + + 4 + 5 + half + down + 2 + + + + + + G + 4 + + 1 + 1 + eighth + down + 1 + begin + + + + F + 3 + + 1 + 1 + eighth + up + 2 + continue + + + + F + 4 + + 1 + 1 + eighth + down + 1 + continue + + + + G + 3 + + 1 + 1 + eighth + up + 2 + end + + + + A + 3 + + 1 + 1 + eighth + up + 2 + begin + + + + B + 3 + + 1 + 1 + eighth + down + 1 + end + + + + C + 4 + + 2 + 1 + quarter + up + 1 + + + 8 + + + + E + 5 + + 4 + 2 + half + up + 1 + + + + D + 5 + + 4 + 2 + half + up + 1 + + + 8 + + + + B + 2 + + 4 + 5 + half + down + 2 + + + + + D + 3 + + 4 + 5 + half + down + 2 + + + + C + 3 + + 4 + 5 + half + down + 2 + + + + + E + 3 + + 4 + 5 + half + down + 2 + + + light-heavy + + + + diff --git a/tests/test_cross_staff_beaming.py b/tests/test_cross_staff_beaming.py new file mode 100644 index 00000000..5eaecfaf --- /dev/null +++ b/tests/test_cross_staff_beaming.py @@ -0,0 +1,20 @@ +import unittest +import os +from tests import MUSICXML_PATH +from partitura import load_musicxml +import numpy as np + +EXAMPLE_FILE = os.path.join(MUSICXML_PATH, "test_cross_staff_beaming.musicxml") + +class CrossStaffBeaming(unittest.TestCase): + def test_cross_staff_single_part_musicxml(self): + score = load_musicxml(EXAMPLE_FILE) + note_array = score.note_array(include_staff=True) + expected_staff = np.array([1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 1, 2, 2, 1, 1]) + cross_staff_mask = (note_array["pitch"] > 52) & (note_array["pitch"] < 72) + note_array_staff = note_array[cross_staff_mask]["staff"] + expected_voice = np.ones(len(note_array_staff), dtype=int) + note_array_voice = note_array[[cross_staff_mask]]["voice"] + self.assertTrue(np.all(note_array_staff == expected_staff)) + self.assertTrue(np.all(note_array_voice == expected_voice)) + From a2e52b8b0776c431df8bdb791c2412aa9c963299 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 20 Sep 2023 11:28:27 +0200 Subject: [PATCH 45/80] minor typo correction. --- tests/test_cross_staff_beaming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cross_staff_beaming.py b/tests/test_cross_staff_beaming.py index 5eaecfaf..1b8ecd8f 100644 --- a/tests/test_cross_staff_beaming.py +++ b/tests/test_cross_staff_beaming.py @@ -14,7 +14,7 @@ def test_cross_staff_single_part_musicxml(self): cross_staff_mask = (note_array["pitch"] > 52) & (note_array["pitch"] < 72) note_array_staff = note_array[cross_staff_mask]["staff"] expected_voice = np.ones(len(note_array_staff), dtype=int) - note_array_voice = note_array[[cross_staff_mask]]["voice"] + note_array_voice = note_array[cross_staff_mask]["voice"] self.assertTrue(np.all(note_array_staff == expected_staff)) self.assertTrue(np.all(note_array_voice == expected_voice)) From 0cea6b0262b744250f49ac25175de49268720d23 Mon Sep 17 00:00:00 2001 From: neosatrapahereje Date: Wed, 20 Sep 2023 12:03:01 +0000 Subject: [PATCH 46/80] Format code with black (bot) --- partitura/io/importmidi.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index ff5421b6..63238640 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -475,12 +475,22 @@ def load_score_midi( ## sanitize time signature, when they are only present in one track, and no global is set # find the number of ts per each track - number_of_time_sig_per_track = [len(time_sigs_by_track[t]) for t in key_sigs_by_track.keys()] + number_of_time_sig_per_track = [ + len(time_sigs_by_track[t]) for t in key_sigs_by_track.keys() + ] # if one track has 0 ts, and another has !=0 ts, and no global_time_sigs is present, sanitize # all key signatures are copied to global, and the track ts are removed - if len(global_time_sigs) == 0 and min(number_of_time_sig_per_track) == 0 and max(number_of_time_sig_per_track)!= 0: - warnings.warn("Sanitizing time signatures. They will be shared across all tracks.") - for ts in [ts for ts_track in time_sigs_by_track.values() for ts in ts_track]: #flattening all track time signatures to a list of ts + if ( + len(global_time_sigs) == 0 + and min(number_of_time_sig_per_track) == 0 + and max(number_of_time_sig_per_track) != 0 + ): + warnings.warn( + "Sanitizing time signatures. They will be shared across all tracks." + ) + for ts in [ + ts for ts_track in time_sigs_by_track.values() for ts in ts_track + ]: # flattening all track time signatures to a list of ts global_time_sigs.append(ts) # now clear all track_ts time_sigs_by_track.clear() From b7e28059c0b0f2a95672b3e38ff53cee77ad8fac Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 20 Sep 2023 14:50:47 +0200 Subject: [PATCH 47/80] corrections requested by reviewers. --- partitura/performance.py | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index aa2c8f15..2b04402a 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -285,8 +285,20 @@ def adjust_offsets_w_sustain( class PerformedNote(dict): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + """ + A dictionary-like object representing a performed note. + + Parameters + ---------- + pnote_dict : dict + A dictionary containing performed note information. + This information can contain the following fields: + "id", "pitch", "note_on", "note_off", "velocity", "track", "channel", "sound_off". + If not provided, the default values will be used. + Pitch, note_on, and note_off are required. + """ + def __init__(self, pnote_dict): + super().__init__(pnote_dict) self["id"] = self.get("id", None) self["pitch"] = self.get("pitch", self["midi_pitch"]) self["note_on"] = self.get("note_on", -1) @@ -295,7 +307,7 @@ def __init__(self, *args, **kwargs): self["track"] = self.get("track", 0) self["channel"] = self.get("channel", 1) self["velocity"] = self.get("velocity", 60) - self.validate_values() + self._validate_values() self._accepted_keys = ["id", "pitch", "note_on", "note_off", "velocity", "track", "channel", "sound_off"] self.__setitem__ = self._setitem_new @@ -306,7 +318,11 @@ def __str__(self): return f"PerformedNote: {self['id']}" def __eq__(self, other): - return self["id"] == other["id"] + if not isinstance(PerformedNote): + return False + if not self.keys() == other.keys(): + return False + return np.all(np.array([self[k] == other[k] for k in self.keys() if k in other.keys()])) def __hash__(self): return hash(self["id"]) @@ -332,20 +348,20 @@ def _setitem_new(self, key, value): elif key == "note_off": # Verify that the note_off is after the note_on if value < self["note_on"]: - raise ValueError(f"note_off must be after note_on") + raise ValueError(f"note_off must be after or equal to note_on") self["sound_off"] = value if self["sound_off"] < value else self["sound_off"] self["note_off"] = value elif key == "note_on": # Verify that the note_on is before the note_off if value > self["note_off"]: - raise ValueError(f"note_on must be before note_off") + raise ValueError(f"note_on must be before or equal to note_off") self["duration_sec"] = self["note_off"] - value self["note_on"] = value elif key == "sound_off": - # Verify that the sound_off is after the note_on - if value < self["note_off"]: - raise ValueError(f"sound_off must be after note_off") + # Verify that the sound_off is after the note_off + if value <= self["note_off"]: + raise ValueError(f"sound_off must be after or equal to note_off") self["sound_off"] = value else: self[key] = value @@ -362,14 +378,14 @@ def __len__(self): def __contains__(self, key): return key in self.keys() - def validate_values(self): + def _validate_values(self): if self["pitch"] > 127 or self["pitch"] < 0: raise ValueError(f"pitch must be between 0 and 127") if self["note_on"] < 0: raise ValueError(f"Note on value provided is invalid, must be greater than or equal to 0") if self["note_off"] < 0 or self["note_off"] < self["note_on"]: raise ValueError(f"Note off value provided is invalid, " - f"must be greater than or equal to 0 and greater than note_on") + f"must be greater than or equal to 0 and greater or equal to note_on") if self["velocity"] > 127 or self["velocity"] < 0: raise ValueError(f"velocity must be between 0 and 127") From c7b4b369047c22e65091d09aaa491c8bd53823f7 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 20 Sep 2023 15:11:27 +0200 Subject: [PATCH 48/80] Removed warnings. corrections requested by reviewers. --- partitura/io/importmatch.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index 14f69828..1aa9aaf8 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -201,19 +201,9 @@ def load_matchfile( np_lines = np.array(raw_lines, dtype=str) # Remove empty lines np_lines = np_lines[np_lines != ""] - len_after_empty_lines = len(np_lines) - if len_raw_lines != len_after_empty_lines: - warnings.warn( - f"Removed {len_raw_lines - len_after_empty_lines} empty lines from match file" - ) # Remove duplicate lines _, idx = np.unique(np_lines, return_index=True) np_lines = np_lines[np.sort(idx)] - len_after_duplicate_lines = len(np_lines) - if len_after_empty_lines != len_after_duplicate_lines: - warnings.warn( - f"Removed {len_after_empty_lines - len_after_duplicate_lines} duplicate lines from match file" - ) # Parse lines f = partial(parse_matchline, version=version, from_matchline_methods=from_matchline_methods) f_vec = np.vectorize(f) From c7768cd7d7c3bee8632102b0773bf521c8fa3e28 Mon Sep 17 00:00:00 2001 From: neosatrapahereje Date: Wed, 20 Sep 2023 13:45:09 +0000 Subject: [PATCH 49/80] Format code with black (bot) --- partitura/io/importmatch.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/partitura/io/importmatch.py b/partitura/io/importmatch.py index 1aa9aaf8..bcadf026 100644 --- a/partitura/io/importmatch.py +++ b/partitura/io/importmatch.py @@ -205,7 +205,9 @@ def load_matchfile( _, idx = np.unique(np_lines, return_index=True) np_lines = np_lines[np.sort(idx)] # Parse lines - f = partial(parse_matchline, version=version, from_matchline_methods=from_matchline_methods) + f = partial( + parse_matchline, version=version, from_matchline_methods=from_matchline_methods + ) f_vec = np.vectorize(f) parsed_lines = f_vec(np_lines).tolist() # Create MatchFile instance From 6028327b97a44c8f1783a7783f13fdb2c49992a4 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Wed, 20 Sep 2023 16:15:07 +0200 Subject: [PATCH 50/80] removed performednote repr method to inherit from parent class. --- partitura/performance.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index 2b04402a..0b0979bb 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -311,9 +311,6 @@ def __init__(self, pnote_dict): self._accepted_keys = ["id", "pitch", "note_on", "note_off", "velocity", "track", "channel", "sound_off"] self.__setitem__ = self._setitem_new - def __repr__(self): - return f"PerformedNote: {self['id']}" - def __str__(self): return f"PerformedNote: {self['id']}" From 6a2ceac5b803ab4b8322c29a36535ee907115273 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Thu, 21 Sep 2023 14:24:22 +0200 Subject: [PATCH 51/80] Fixes #304 Set temporarily the recursion limit higher to avoid SystemError with deepcopy and recursion depth. --- partitura/score.py | 14 +++++++++++++- partitura/utils/music.py | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/partitura/score.py b/partitura/score.py index 3a0589f0..eff87fde 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -16,7 +16,7 @@ # import copy from partitura.utils.music import MUSICAL_BEATS, INTERVALCLASSES -import warnings +import warnings, sys import numpy as np from scipy.interpolate import PPoly from typing import Union, List, Optional, Iterator, Iterable as Itertype @@ -4614,7 +4614,13 @@ def unfold_part_maximal(score: ScoreLike, update_ids=True, ignore_leaps=True): """ if isinstance(score, Score): + # Copy needs to be deep, otherwise the recursion limit will be exceeded + old_recursion_depth = sys.getrecursionlimit() + sys.setrecursionlimit(10000) + # Deep copy of score new_score = deepcopy(score) + # Reset recursion limit to previous value to avoid side effects + sys.setrecursionlimit(old_recursion_depth) new_partlist = list() for score in new_score.parts: unfolded_part = unfold_part_maximal( @@ -4652,7 +4658,13 @@ def unfold_part_minimal(score: ScoreLike): """ if isinstance(score, Score): + # Copy needs to be deep, otherwise the recursion limit will be exceeded + old_recursion_depth = sys.getrecursionlimit() + sys.setrecursionlimit(10000) + # Deep copy of score unfolded_score = deepcopy(score) + # Reset recursion limit to previous value to avoid side effects + sys.setrecursionlimit(old_recursion_depth) new_partlist = list() for part in unfolded_score.parts: unfolded_part = unfold_part_minimal(part) diff --git a/partitura/utils/music.py b/partitura/utils/music.py index 5a24b708..a602ddea 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -488,8 +488,15 @@ def transpose(score: ScoreLike, interval: Interval) -> ScoreLike: Transposed score. """ import partitura.score as s + import sys + # Copy needs to be deep, otherwise the recursion limit will be exceeded + old_recursion_depth = sys.getrecursionlimit() + sys.setrecursionlimit(10000) + # Deep copy of score new_score = copy.deepcopy(score) + # Reset recursion limit to previous value to avoid side effects + sys.setrecursionlimit(old_recursion_depth) if isinstance(score, s.Score): for part in new_score.parts: transpose(part, interval) From 4027a9a104b0de189b4d24fcc0f5e84a8d89f9cc Mon Sep 17 00:00:00 2001 From: neosatrapahereje Date: Thu, 21 Sep 2023 12:39:02 +0000 Subject: [PATCH 52/80] Format code with black (bot) --- partitura/performance.py | 30 ++++++++++++++++++++++++------ partitura/utils/music.py | 5 +++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index 0b0979bb..eb518737 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -297,6 +297,7 @@ class PerformedNote(dict): If not provided, the default values will be used. Pitch, note_on, and note_off are required. """ + def __init__(self, pnote_dict): super().__init__(pnote_dict) self["id"] = self.get("id", None) @@ -308,7 +309,16 @@ def __init__(self, pnote_dict): self["channel"] = self.get("channel", 1) self["velocity"] = self.get("velocity", 60) self._validate_values() - self._accepted_keys = ["id", "pitch", "note_on", "note_off", "velocity", "track", "channel", "sound_off"] + self._accepted_keys = [ + "id", + "pitch", + "note_on", + "note_off", + "velocity", + "track", + "channel", + "sound_off", + ] self.__setitem__ = self._setitem_new def __str__(self): @@ -319,7 +329,9 @@ def __eq__(self, other): return False if not self.keys() == other.keys(): return False - return np.all(np.array([self[k] == other[k] for k in self.keys() if k in other.keys()])) + return np.all( + np.array([self[k] == other[k] for k in self.keys() if k in other.keys()]) + ) def __hash__(self): return hash(self["id"]) @@ -346,7 +358,9 @@ def _setitem_new(self, key, value): # Verify that the note_off is after the note_on if value < self["note_on"]: raise ValueError(f"note_off must be after or equal to note_on") - self["sound_off"] = value if self["sound_off"] < value else self["sound_off"] + self["sound_off"] = ( + value if self["sound_off"] < value else self["sound_off"] + ) self["note_off"] = value elif key == "note_on": # Verify that the note_on is before the note_off @@ -379,10 +393,14 @@ def _validate_values(self): if self["pitch"] > 127 or self["pitch"] < 0: raise ValueError(f"pitch must be between 0 and 127") if self["note_on"] < 0: - raise ValueError(f"Note on value provided is invalid, must be greater than or equal to 0") + raise ValueError( + f"Note on value provided is invalid, must be greater than or equal to 0" + ) if self["note_off"] < 0 or self["note_off"] < self["note_on"]: - raise ValueError(f"Note off value provided is invalid, " - f"must be greater than or equal to 0 and greater or equal to note_on") + raise ValueError( + f"Note off value provided is invalid, " + f"must be greater than or equal to 0 and greater or equal to note_on" + ) if self["velocity"] > 127 or self["velocity"] < 0: raise ValueError(f"velocity must be between 0 and 127") diff --git a/partitura/utils/music.py b/partitura/utils/music.py index cf8d4fbe..6d859084 100644 --- a/partitura/utils/music.py +++ b/partitura/utils/music.py @@ -3283,7 +3283,6 @@ def slice_ppart_by_time( new_pr["time_tick"] = int(2 * ppq * pr["time"]) programs_slice.append(new_pr) - notes_slice = [] note_id = 0 for note in ppart.notes: @@ -3327,7 +3326,9 @@ def slice_ppart_by_time( break # Create slice PerformedPart - ppart_slice = PerformedPart(notes=notes_slice, programs=programs_slice, controls=controls_slice, ppq=ppq) + ppart_slice = PerformedPart( + notes=notes_slice, programs=programs_slice, controls=controls_slice, ppq=ppq + ) # set threshold property after creating notes list to update 'sound_offset' values ppart_slice.sustain_pedal_threshold = ppart.sustain_pedal_threshold From f67a33153bacec24000c8f90ab4ee47aec0be944 Mon Sep 17 00:00:00 2001 From: huispaty Date: Thu, 21 Sep 2023 17:14:48 +0200 Subject: [PATCH 53/80] removed special parsing for mozart scores --- partitura/io/matchfile_utils.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/partitura/io/matchfile_utils.py b/partitura/io/matchfile_utils.py index 864d6372..d32eeaa7 100644 --- a/partitura/io/matchfile_utils.py +++ b/partitura/io/matchfile_utils.py @@ -885,23 +885,9 @@ def __str__(self): return self.value @classmethod - def from_string(cls, string: str) -> MatchTempoIndication: - - # NOTE particularities of the BpM dataset.... - if string is not None: - if 'Rond' in string: - content = string.split(' ') - content = [content[-1]] - elif 'Alla' in string: # for kv331_3 - content = ['Allegretto'] - elif 'Menuetto' in string: - content = ['Menuetto'] - else: content = interpret_as_list(string) - else: - content = interpret_as_list(string) + content = interpret_as_list(string) return content - def interpret_as_tempo_indication(value: str) -> MatchTempoIndication: tempo_indication = MatchTempoIndication.from_string(value) @@ -911,7 +897,6 @@ def format_tempo_indication(value: MatchTempoIndication) -> str: value.is_list = False return str(value) - ## Miscellaneous utils From c143358306fa8a9d99bc758594575042d1d3f122 Mon Sep 17 00:00:00 2001 From: huispaty Date: Thu, 21 Sep 2023 18:13:13 +0200 Subject: [PATCH 54/80] corrected mozart var testfile --- tests/data/match/mozart_k265_var1.match | 75 ++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/tests/data/match/mozart_k265_var1.match b/tests/data/match/mozart_k265_var1.match index 0fd2f9a5..35dc0fe0 100644 --- a/tests/data/match/mozart_k265_var1.match +++ b/tests/data/match/mozart_k265_var1.match @@ -3,7 +3,7 @@ info(piece,mozart_k265_var1). info(scoreFileName,mozart_k265_var1.musicxml). info(midiFileName,mozart_k265_var1.mid). info(composer,W. A. Mozart). -info(performer,A Pianist). +info(performer,Carlos Cancino-Chacón). info(midiClockUnits,480). info(midiClockRate,500000). scoreprop(keySignature,C,1:1,0,0.0000). @@ -227,3 +227,76 @@ snote(n221,[B,n],4,23:2,1/8,1/8,45.5000,46.0000,[v1,staff1])-note(n216,71,21540, snote(n224,[C,n],5,24:1,0,1/4,46.0000,47.0000,[v1,staff1])-note(n217,72,21793,22915,48,1,1). snote(n226,[C,n],4,24:1,0,1/4,46.0000,47.0000,[v5,staff2])-note(n218,60,21819,22029,49,1,1). snote(n227,[C,n],3,24:2,0,1/4,47.0000,48.0000,[v5,staff2])-note(n219,48,22393,22966,50,1,1). +sustain(0,0). +sustain(3316,67). +sustain(3346,127). +sustain(4064,71). +sustain(4094,45). +sustain(4123,0). +sustain(4384,60). +sustain(4412,127). +sustain(4929,73). +sustain(4958,55). +sustain(4987,41). +sustain(5015,0). +sustain(5246,58). +sustain(5276,127). +sustain(5706,60). +sustain(5735,48). +sustain(5763,0). +sustain(7436,69). +sustain(7464,127). +sustain(7866,62). +sustain(7894,44). +sustain(7923,0). +sustain(8385,68). +sustain(8415,127). +sustain(8787,62). +sustain(8817,46). +sustain(8846,0). +sustain(9279,127). +sustain(9566,53). +sustain(9594,40). +sustain(9622,0). +sustain(11006,67). +sustain(11036,127). +sustain(11294,71). +sustain(11323,55). +sustain(11350,0). +sustain(11668,62). +sustain(11698,127). +sustain(12244,53). +sustain(12273,0). +sustain(12590,60). +sustain(12620,127). +sustain(13165,73). +sustain(13194,64). +sustain(13223,53). +sustain(13251,0). +sustain(13511,59). +sustain(13541,127). +sustain(14145,62). +sustain(14174,50). +sustain(14202,37). +sustain(14231,0). +sustain(14923,40). +sustain(14952,67). +sustain(14981,127). +sustain(15498,62). +sustain(15527,53). +sustain(15555,0). +sustain(18262,54). +sustain(18292,71). +sustain(18322,127). +sustain(18955,50). +sustain(18983,0). +sustain(19271,41). +sustain(19301,127). +sustain(19819,57). +sustain(19847,40). +sustain(19876,0). +sustain(20077,43). +sustain(20107,127). +sustain(20682,61). +sustain(20711,50). +sustain(20740,0). \ No newline at end of file From 1262c54d35bd486d34c91b18d0554bcd9ba9d4cd Mon Sep 17 00:00:00 2001 From: manoskary Date: Fri, 22 Sep 2023 10:39:41 +0000 Subject: [PATCH 55/80] Format code with black (bot) --- partitura/io/exportmatch.py | 43 ++++++++++++++++++--------------- partitura/io/exportmidi.py | 4 +-- partitura/io/importmusicxml.py | 9 +++---- partitura/io/matchfile_utils.py | 15 +++++++----- partitura/io/matchlines_v1.py | 1 + partitura/score.py | 5 ++-- 6 files changed, 42 insertions(+), 35 deletions(-) diff --git a/partitura/io/exportmatch.py b/partitura/io/exportmatch.py index 193b080f..03e8ed3d 100644 --- a/partitura/io/exportmatch.py +++ b/partitura/io/exportmatch.py @@ -187,7 +187,7 @@ def matchfile_from_alignment( alignment=alignment, remove_ornaments=True, ) - + measures = np.array(list(spart.iter_all(score.Measure))) measure_starts_divs = np.array([m.start.t for m in measures]) measure_starts_beats = beat_map(measure_starts_divs) @@ -328,11 +328,14 @@ def matchfile_from_alignment( if fermata is not None: score_attributes_list.append("fermata") - + if isinstance(snote, score.GraceNote): score_attributes_list.append("grace") - - if diff_score_version_notes is not None and snote.id in diff_score_version_notes: + + if ( + diff_score_version_notes is not None + and snote.id in diff_score_version_notes + ): score_attributes_list.append("diff_score_version") score_info[snote.id] = MatchSnote( @@ -360,19 +363,19 @@ def matchfile_from_alignment( # # NOTE time position is hardcoded, not pretty... Assumes there is only one tempo indication at the beginning of the score if tempo_indication is not None: score_tempo_direction_header = make_scoreprop( - version=version, - attribute="tempoIndication", - value=MatchTempoIndication( - tempo_indication, - is_list=False, - ), - measure=measure_starts[0][0], - beat=1, - offset=0, - time_in_beats=measure_starts[0][2], - ) + version=version, + attribute="tempoIndication", + value=MatchTempoIndication( + tempo_indication, + is_list=False, + ), + measure=measure_starts[0][0], + beat=1, + offset=0, + time_in_beats=measure_starts[0][2], + ) scoreprop_lines["tempo_indication"].append(score_tempo_direction_header) - + perf_info = dict() pnote_sort_info = dict() for pnote in ppart.notes: @@ -412,8 +415,8 @@ def matchfile_from_alignment( voice_overlap_note_ids = [] if len(duplicates) > 0: duplicate_idx = np.concatenate(np.array(list(duplicates.values()))).flatten() - voice_overlap_note_ids = list(sna[duplicate_idx]['id']) - + voice_overlap_note_ids = list(sna[duplicate_idx]["id"]) + for al_note in alignment: label = al_note["label"] @@ -427,7 +430,7 @@ def matchfile_from_alignment( elif label == "deletion": snote = score_info[al_note["score_id"]] if al_note["score_id"] in voice_overlap_note_ids: - snote.ScoreAttributesList.append('voice_overlap') + snote.ScoreAttributesList.append("voice_overlap") deletion_line = MatchSnoteDeletion(version=version, snote=snote) note_lines.append(deletion_line) sort_stime.append(snote_sort_info[al_note["score_id"]]) @@ -451,7 +454,7 @@ def matchfile_from_alignment( note_lines.append(ornament_line) sort_stime.append(pnote_sort_info[al_note["performance_id"]]) - + # sort notes by score onset (performed insertions are sorted # according to the interpolation map sort_stime = np.array(sort_stime) diff --git a/partitura/io/exportmidi.py b/partitura/io/exportmidi.py index 28ce7d83..c1639bb1 100644 --- a/partitura/io/exportmidi.py +++ b/partitura/io/exportmidi.py @@ -202,7 +202,7 @@ def save_performance_midi( track_events[tr][min(timepoints)].append( Message("program_change", program=0, channel=ch) ) - + midi_type = 0 if len(track_events) == 1 else 1 mf = MidiFile(type=midi_type, ticks_per_beat=ppq) @@ -222,7 +222,7 @@ def save_performance_midi( if merge_tracks_save and len(mf.tracks) > 1: mf.tracks = [merge_tracks(mf.tracks)] - + if out is not None: if hasattr(out, "write"): mf.save(file=out) diff --git a/partitura/io/importmusicxml.py b/partitura/io/importmusicxml.py index 1e0fe45c..b586b9f6 100644 --- a/partitura/io/importmusicxml.py +++ b/partitura/io/importmusicxml.py @@ -261,7 +261,7 @@ def load_musicxml( subtitle = None lyricist = None copyright = None - + # The work tag is preferred for the title of the score, otherwise # this method will search in the credit tags work_info_el = document.find("work") @@ -276,12 +276,12 @@ def load_musicxml( e=work_info_el, tag="work-number", as_type=str, - ) + ) work_title = scid work_number = scidn - movement_title_el = document.find('.//movement-title') - movement_number_el = document.find('.//movement-number') + movement_title_el = document.find(".//movement-title") + movement_number_el = document.find(".//movement-number") if movement_title_el is not None: movement_title = movement_title_el.text if movement_number_el is not None: @@ -914,7 +914,6 @@ def _handle_direction(e, position, part, ongoing): warnings.warn("Did not find a wedge start element for wedge stop!") elif dt.tag == "dashes": - # start/stop/continue dashes_type = get_value_from_attribute(dt, "type", str) number = get_value_from_attribute(dt, "number", int) or 1 diff --git a/partitura/io/matchfile_utils.py b/partitura/io/matchfile_utils.py index d32eeaa7..e6b48dc4 100644 --- a/partitura/io/matchfile_utils.py +++ b/partitura/io/matchfile_utils.py @@ -873,14 +873,14 @@ def format_time_signature_list(value: MatchTimeSignature) -> str: class MatchTempoIndication(MatchParameter): def __init__( - self, - value: str, - is_list: bool = False, + self, + value: str, + is_list: bool = False, ): super().__init__() self.value = self.from_string(value)[0] self.is_list = is_list - + def __str__(self): return self.value @@ -888,15 +888,18 @@ def __str__(self): def from_string(cls, string: str) -> MatchTempoIndication: content = interpret_as_list(string) return content - + + def interpret_as_tempo_indication(value: str) -> MatchTempoIndication: tempo_indication = MatchTempoIndication.from_string(value) return tempo_indication + def format_tempo_indication(value: MatchTempoIndication) -> str: value.is_list = False return str(value) - + + ## Miscellaneous utils diff --git a/partitura/io/matchlines_v1.py b/partitura/io/matchlines_v1.py index 1b1b3fbd..1fdcfe69 100644 --- a/partitura/io/matchlines_v1.py +++ b/partitura/io/matchlines_v1.py @@ -227,6 +227,7 @@ def from_instance( format_fun=format_fun, ) + SCOREPROP_LINE = { Version(1, 0, 0): { "timeSignature": ( diff --git a/partitura/score.py b/partitura/score.py index d54c55bc..596fc3ba 100644 --- a/partitura/score.py +++ b/partitura/score.py @@ -624,7 +624,7 @@ def tempo_directions(self): list List of TempoDirection objects - """ + """ return [e for e in self.iter_all(TempoDirection, include_subclasses=True)] @property @@ -2806,7 +2806,7 @@ def __str__(self): else: return f'{super().__str__()} "{self.text}"' - + class LoudnessDirection(Direction): pass @@ -2842,6 +2842,7 @@ class ConstantLoudnessDirection(ConstantDirection, LoudnessDirection): class ConstantTempoDirection(ConstantDirection, TempoDirection): pass + class ConstantArticulationDirection(ConstantDirection, ArticulationDirection): pass From e0db0412ed532b3b66f8306c933c2d06b23cb41b Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:15:11 +0200 Subject: [PATCH 56/80] Update conf.py for release 1.3.2 --- docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index f19709c9..18360418 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,9 +29,9 @@ # built documents. # # The short X.Y version. -version = "1.3.1" # pkg_resources.get_distribution("partitura").version +version = "1.3.2" # pkg_resources.get_distribution("partitura").version # The full version, including alpha/beta/rc tags. -release = "1.3.1" +release = "1.3.2" # # The full version, including alpha/beta/rc tags # release = pkg_resources.get_distribution("partitura").version From 70f558f2bd8ab66dc1a89b6a0e25574382c14442 Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:16:23 +0200 Subject: [PATCH 57/80] Update CHANGES.md --- CHANGES.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 0f73f721..b3afea61 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,34 @@ Release Notes ============= +Version 1.3.2 (Released on 2023-09-22) +-------------------------------------- + +New Features +------------ + +* minimal unfolding for part +* updated Musescore parser for version 4 +* `load_score` auto-selects parser based on file type +* new attributes for `Score` object for capturing meta information +* new score note attributes in matchfile export (`grace`, `voice_overlap`) +* new `tempo_indication` score property line in matchfile export + +Bug Fixes +------------ +* Fixed bug: #297 +* Fixed bug: #304 +* Fixed bug: #306 +* Fixed bug: #308 +* Fixed bug: #310 +* Fixed bug: #315 + +Other Changes +------------ +* new unit test for cross-staff beaming for musicxml +* note ID numbering starts at 1 (instead of 0) when creating a performed part from MIDI + + Version 1.3.1 (Released on 2023-07-06) -------------------------------------- From f0ffdd61c39b8101d55e05bcc03e7f747722c360 Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:16:47 +0200 Subject: [PATCH 58/80] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b6b93d3f..83ed6db0 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ EMAIL = "partitura-users@googlegroups.com" AUTHOR = "Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier" REQUIRES_PYTHON = ">=3.7" -VERSION = "1.3.1" +VERSION = "1.3.2" # What packages are required for this module to be executed? REQUIRED = ["numpy", "scipy", "lxml", "lark-parser", "xmlschema", "mido"] From 63e7419d4e8bc1269c0fe3fbbfc42102b80b3e78 Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 16:00:17 +0200 Subject: [PATCH 59/80] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b3afea61..609f3fa2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,7 @@ Version 1.3.2 (Released on 2023-09-22) New Features ------------ - +* new class for performed notes * minimal unfolding for part * updated Musescore parser for version 4 * `load_score` auto-selects parser based on file type From 6579a0f65ca308924e95283c77053f17a07abfe3 Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 17:01:34 +0200 Subject: [PATCH 60/80] Update __init__.py --- partitura/io/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index 2228321a..49d6b85c 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -81,7 +81,7 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: filename=filename, force_note_ids=force_note_ids, ) - elif extension in [".mscz", ".mscx", ".musescore", ".mscore", ".ms"]: + elif extension in [".mscz", ".mscx", ".musescore", ".mscore", ".ms", ".kar", ".md", ".cap", ".capx", ".bww", ".mgu", ".sgu", ".ove", ".scw", ".ptb", ".gtp", ".gp3", ".gp4", ".gp5", ".gpx", ".gp"]: # Load MuseScore return load_via_musescore( filename=filename, From 8c15d146c1094d5fa363d1609e7b691d0c86362b Mon Sep 17 00:00:00 2001 From: huispaty Date: Fri, 22 Sep 2023 15:01:55 +0000 Subject: [PATCH 61/80] Format code with black (bot) --- partitura/io/__init__.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/partitura/io/__init__.py b/partitura/io/__init__.py index 49d6b85c..4ad2cd47 100644 --- a/partitura/io/__init__.py +++ b/partitura/io/__init__.py @@ -81,7 +81,29 @@ def load_score(filename: PathLike, force_note_ids="keep") -> Score: filename=filename, force_note_ids=force_note_ids, ) - elif extension in [".mscz", ".mscx", ".musescore", ".mscore", ".ms", ".kar", ".md", ".cap", ".capx", ".bww", ".mgu", ".sgu", ".ove", ".scw", ".ptb", ".gtp", ".gp3", ".gp4", ".gp5", ".gpx", ".gp"]: + elif extension in [ + ".mscz", + ".mscx", + ".musescore", + ".mscore", + ".ms", + ".kar", + ".md", + ".cap", + ".capx", + ".bww", + ".mgu", + ".sgu", + ".ove", + ".scw", + ".ptb", + ".gtp", + ".gp3", + ".gp4", + ".gp5", + ".gpx", + ".gp", + ]: # Load MuseScore return load_via_musescore( filename=filename, From e383d6275d48a04c42252813d064085d3b1ca50a Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 17:02:42 +0200 Subject: [PATCH 62/80] Update importkern.py --- partitura/io/importkern.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/partitura/io/importkern.py b/partitura/io/importkern.py index c48f6775..da0fecea 100644 --- a/partitura/io/importkern.py +++ b/partitura/io/importkern.py @@ -546,9 +546,6 @@ def parse_kern(kern_path: PathLike) -> np.ndarray: """ with open(kern_path, encoding="cp437") as file: lines = file.read().splitlines() - if lines[0][0] == "<": - # we are using a heuristic to stop the import if we are dealing with a XML file - raise Exception("Invalid Kern file") d = [line.split("\t") for line in lines if not line.startswith("!")] striped_parts = list() merge_index = [] From 7bdcc78b8e781f44d1bcf46b57118a9db5c35928 Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 17:03:18 +0200 Subject: [PATCH 63/80] Update setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 83ed6db0..10c3bbf2 100644 --- a/setup.py +++ b/setup.py @@ -14,9 +14,9 @@ KEYWORDS = "music notation musicxml midi" 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" +AUTHOR = "Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier, Patricia Hu" REQUIRES_PYTHON = ">=3.7" -VERSION = "1.3.2" +VERSION = "1.4.0" # What packages are required for this module to be executed? REQUIRED = ["numpy", "scipy", "lxml", "lark-parser", "xmlschema", "mido"] From 0fce07679fa0d4d4ec9a14152bceb2a5c32b6920 Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 17:13:08 +0200 Subject: [PATCH 64/80] Update test_musescore.py --- tests/test_musescore.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/tests/test_musescore.py b/tests/test_musescore.py index 156965a1..e8f58cef 100644 --- a/tests/test_musescore.py +++ b/tests/test_musescore.py @@ -13,19 +13,20 @@ from partitura.utils import compute_pianoroll from lxml import etree from xmlschema.names import XML_NAMESPACE -from partitura.io import load_score, load_via_musescore +from partitura.io import load_score, load_via_musescore, find_musescore, MuseScoreNotFoundException import platform import numpy as np from pathlib import Path -# This tests are commented, because it is not possible to run MuseScore on Github server -# uncomment to run them locally instead - -# class TestImportMusescore(unittest.TestCase): -# def test_epfl_scores(self): -# score = load_via_musescore(MUSESCORE_TESTFILES[0]) -# self.assertTrue(len(score.parts) == 1) -# # try the generic loading function -# score = load_score(MUSESCORE_TESTFILES[0]) -# self.assertTrue(len(score.parts) == 1) \ No newline at end of file +try: + find_musescore() + class TestImportMusescore(unittest.TestCase): + def test_epfl_scores(self): + score = load_via_musescore(MUSESCORE_TESTFILES[0]) + self.assertTrue(len(score.parts) == 1) + # try the generic loading function + score = load_score(MUSESCORE_TESTFILES[0]) + self.assertTrue(len(score.parts) == 1) +except MuseScoreNotFoundException: + pass From 5a400bd5bdea345b8487e67e253d8d0fe3be423f Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 17:13:39 +0200 Subject: [PATCH 65/80] Update conf.py --- docs/source/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 18360418..f71055dd 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,9 +29,9 @@ # built documents. # # The short X.Y version. -version = "1.3.2" # pkg_resources.get_distribution("partitura").version +version = "1.4.0" # pkg_resources.get_distribution("partitura").version # The full version, including alpha/beta/rc tags. -release = "1.3.2" +release = "1.4.0" # # The full version, including alpha/beta/rc tags # release = pkg_resources.get_distribution("partitura").version From adc00dbf3909bfbd8afc0b404c3e8e59ee3b244f Mon Sep 17 00:00:00 2001 From: Patricia Hu <66968965+huispaty@users.noreply.github.com> Date: Fri, 22 Sep 2023 17:14:02 +0200 Subject: [PATCH 66/80] Update CHANGES.md --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 609f3fa2..8fd051cb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ Release Notes ============= -Version 1.3.2 (Released on 2023-09-22) +Version 1.4.0 (Released on 2023-09-22) -------------------------------------- New Features From 69bfe5a4203bf045a0426fbc08ea1b224c06556d Mon Sep 17 00:00:00 2001 From: huispaty Date: Fri, 22 Sep 2023 17:24:46 +0200 Subject: [PATCH 67/80] fixed musescore testing --- tests/test_musescore.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/test_musescore.py b/tests/test_musescore.py index e8f58cef..feec7f83 100644 --- a/tests/test_musescore.py +++ b/tests/test_musescore.py @@ -13,20 +13,21 @@ from partitura.utils import compute_pianoroll from lxml import etree from xmlschema.names import XML_NAMESPACE -from partitura.io import load_score, load_via_musescore, find_musescore, MuseScoreNotFoundException +from partitura.io import load_score, load_via_musescore +from partitura.io.musescore import find_musescore, MuseScoreNotFoundException import platform import numpy as np from pathlib import Path try: - find_musescore() - class TestImportMusescore(unittest.TestCase): - def test_epfl_scores(self): - score = load_via_musescore(MUSESCORE_TESTFILES[0]) - self.assertTrue(len(score.parts) == 1) - # try the generic loading function - score = load_score(MUSESCORE_TESTFILES[0]) - self.assertTrue(len(score.parts) == 1) + if find_musescore(): + class TestImportMusescore(unittest.TestCase): + def test_epfl_scores(self): + score = load_via_musescore(MUSESCORE_TESTFILES[0]) + self.assertTrue(len(score.parts) == 1) + # try the generic loading function + score = load_score(MUSESCORE_TESTFILES[0]) + self.assertTrue(len(score.parts) == 1) except MuseScoreNotFoundException: pass From 027344e0b16481e80e9b03151b29c2f043d4c3c2 Mon Sep 17 00:00:00 2001 From: Emmanouil Karystinaios Date: Fri, 22 Sep 2023 17:45:44 +0200 Subject: [PATCH 68/80] Update musescore.py Added an extra clause in find musescore to check whether a screen is available. --- partitura/io/musescore.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 6f50e304..26fa355b 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -91,6 +91,9 @@ def find_musescore(): ) else: raise MuseScoreNotFoundException() + if "DISPLAY" not in os.environ: + raise MuseScoreNotFoundException("Musescore Executable was found, but a screen is missing. Musescore needs a screen to load scores") + return mscore_exec From 4e9e1d97207b9c2ee55c5ece0fee7a5c8608cd4d Mon Sep 17 00:00:00 2001 From: manoskary Date: Fri, 22 Sep 2023 15:46:05 +0000 Subject: [PATCH 69/80] Format code with black (bot) --- partitura/io/musescore.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 26fa355b..7b976993 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -92,8 +92,10 @@ def find_musescore(): else: raise MuseScoreNotFoundException() if "DISPLAY" not in os.environ: - raise MuseScoreNotFoundException("Musescore Executable was found, but a screen is missing. Musescore needs a screen to load scores") - + raise MuseScoreNotFoundException( + "Musescore Executable was found, but a screen is missing. Musescore needs a screen to load scores" + ) + return mscore_exec From eb94c13a2696d5ea0b33f9cd03c3dd1f9e31b7ba Mon Sep 17 00:00:00 2001 From: huispaty Date: Mon, 25 Sep 2023 12:53:12 +0200 Subject: [PATCH 70/80] reverted performance note numbering --- partitura/io/importmidi.py | 2 +- tests/data/match/mozart_k265_var1.match | 438 ++++++++++++------------ tests/test_tonal_tension.py | 6 +- 3 files changed, 223 insertions(+), 223 deletions(-) diff --git a/partitura/io/importmidi.py b/partitura/io/importmidi.py index 77ddf749..5843b781 100644 --- a/partitura/io/importmidi.py +++ b/partitura/io/importmidi.py @@ -217,7 +217,7 @@ def load_performance_midi( # add note id to every note for k, note in enumerate(notes): - note["id"] = f"n{k+1}" + note["id"] = f"n{k}" if len(notes) > 0 or len(controls) > 0 or len(programs) > 0: pp = performance.PerformedPart( diff --git a/tests/data/match/mozart_k265_var1.match b/tests/data/match/mozart_k265_var1.match index 35dc0fe0..9c29507e 100644 --- a/tests/data/match/mozart_k265_var1.match +++ b/tests/data/match/mozart_k265_var1.match @@ -8,225 +8,225 @@ info(midiClockUnits,480). info(midiClockRate,500000). scoreprop(keySignature,C,1:1,0,0.0000). scoreprop(timeSignature,2/4,1:1,0,0.0000). -snote(n1,[D,n],5,1:1,0,1/16,0.0000,0.2500,[v1,staff1])-note(n2,74,692,773,72,1,1). -snote(n9,[C,n],3,1:1,0,1/4,0.0000,1.0000,[v5,staff2])-note(n1,48,683,747,70,1,1). -snote(n2,[C,n],5,1:1,1/16,1/16,0.2500,0.5000,[v1,staff1])-note(n3,72,819,892,53,1,1). -snote(n3,[B,n],4,1:1,1/8,1/16,0.5000,0.7500,[v1,staff1])-note(n4,71,914,980,54,1,1). -snote(n4,[C,n],5,1:1,3/16,1/16,0.7500,1.0000,[v1,staff1])-note(n5,72,1009,1098,48,1,1). -snote(n5,[B,n],4,1:2,0,1/16,1.0000,1.2500,[v1,staff1])-note(n7,71,1128,1174,61,1,1). -snote(n10,[C,n],4,1:2,0,1/4,1.0000,2.0000,[v5,staff2])-note(n6,60,1122,1160,67,1,1). -snote(n6,[C,n],5,1:2,1/16,1/16,1.2500,1.5000,[v1,staff1])-note(n8,72,1233,1284,44,1,1). -snote(n7,[B,n],4,1:2,1/8,1/16,1.5000,1.7500,[v1,staff1])-note(n9,71,1316,1410,65,1,1). -snote(n8,[C,n],5,1:2,3/16,1/16,1.7500,2.0000,[v1,staff1])-note(n10,72,1420,1490,61,1,1). -snote(n11,[A,n],5,2:1,0,1/16,2.0000,2.2500,[v1,staff1])-note(n12,81,1556,1637,55,1,1). -snote(n19,[E,n],4,2:1,0,1/4,2.0000,3.0000,[v5,staff2])-note(n11,64,1541,1614,75,1,1). -snote(n12,[G,n],5,2:1,1/16,1/16,2.2500,2.5000,[v1,staff1])-note(n13,79,1683,1752,62,1,1). -snote(n13,[F,#],5,2:1,1/8,1/16,2.5000,2.7500,[v1,staff1])-note(n14,78,1785,1831,52,1,1). -snote(n14,[G,n],5,2:1,3/16,1/16,2.7500,3.0000,[v1,staff1])-note(n15,79,1893,1949,77,1,1). -snote(n15,[F,#],5,2:2,0,1/16,3.0000,3.2500,[v1,staff1])-note(n16,78,1984,2041,60,1,1). -snote(n20,[C,n],4,2:2,0,1/4,3.0000,4.0000,[v5,staff2])-note(n17,60,2002,2043,49,1,1). -snote(n16,[G,n],5,2:2,1/16,1/16,3.2500,3.5000,[v1,staff1])-note(n18,79,2100,2130,52,1,1). -snote(n17,[F,#],5,2:2,1/8,1/16,3.5000,3.7500,[v1,staff1])-note(n19,78,2180,2235,64,1,1). -snote(n18,[G,n],5,2:2,3/16,1/16,3.7500,4.0000,[v1,staff1])-note(n20,79,2275,2317,54,1,1). -snote(n21,[G,#],5,3:1,0,1/16,4.0000,4.2500,[v1,staff1])-note(n21,80,2392,2454,61,1,1). -snote(n29,[F,n],4,3:1,0,1/4,4.0000,5.0000,[v5,staff2])-note(n22,65,2416,2457,61,1,1). -snote(n22,[A,n],5,3:1,1/16,1/16,4.2500,4.5000,[v1,staff1])-note(n23,81,2495,2600,61,1,1). -snote(n23,[C,n],6,3:1,1/8,1/16,4.5000,4.7500,[v1,staff1])-note(n24,84,2611,2722,62,1,1). -snote(n24,[B,n],5,3:1,3/16,1/16,4.7500,5.0000,[v1,staff1])-note(n25,83,2722,2796,51,1,1). -snote(n25,[D,n],6,3:2,0,1/16,5.0000,5.2500,[v1,staff1])-note(n26,86,2807,2939,61,1,1). -snote(n30,[C,n],4,3:2,0,1/4,5.0000,6.0000,[v5,staff2])-note(n27,60,2836,2890,45,1,1). -snote(n26,[C,n],6,3:2,1/16,1/16,5.2500,5.5000,[v1,staff1])-note(n28,84,2939,3024,55,1,1). -snote(n27,[B,n],5,3:2,1/8,1/16,5.5000,5.7500,[v1,staff1])-note(n29,83,3033,3093,64,1,1). -snote(n28,[A,n],5,3:2,3/16,1/16,5.7500,6.0000,[v1,staff1])-note(n30,81,3138,3162,52,1,1). -snote(n31,[A,n],5,4:1,0,1/16,6.0000,6.2500,[v1,staff1])-note(n32,81,3280,3364,68,1,1). -snote(n39,[C,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n31,60,3275,3538,42,1,1). -snote(n40,[E,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n33,64,3291,3526,59,1,1). -snote(n32,[G,n],5,4:1,1/16,1/16,6.2500,6.5000,[v1,staff1])-note(n34,79,3413,3499,62,1,1). -snote(n33,[E,n],6,4:1,1/8,1/16,6.5000,6.7500,[v1,staff1])-note(n35,88,3531,3657,69,1,1). -snote(n34,[D,n],6,4:1,3/16,1/16,6.7500,7.0000,[v1,staff1])-note(n36,86,3639,3762,74,1,1). -snote(n35,[C,n],6,4:2,0,1/16,7.0000,7.2500,[v1,staff1])-note(n37,84,3758,3859,62,1,1). -snote(n36,[B,n],5,4:2,1/16,1/16,7.2500,7.5000,[v1,staff1])-note(n38,83,3845,3912,64,1,1). -snote(n37,[A,n],5,4:2,1/8,1/16,7.5000,7.7500,[v1,staff1])-note(n39,81,3933,3976,59,1,1). -snote(n38,[G,n],5,4:2,3/16,1/16,7.7500,8.0000,[v1,staff1])-note(n41,79,4058,4069,58,1,1). -snote(n42,[C,#],4,4:2,3/16,1/16,7.7500,8.0000,[v5,staff2])-note(n40,61,4050,4094,60,1,1). -snote(n43,[G,n],5,5:1,0,1/16,8.0000,8.2500,[v1,staff1])-note(n42,79,4187,4262,63,1,1). -snote(n51,[D,n],4,5:1,0,1/4,8.0000,9.0000,[v5,staff2])-note(n43,62,4203,4563,79,1,1). -snote(n44,[F,n],5,5:1,1/16,1/16,8.2500,8.5000,[v1,staff1])-note(n44,77,4289,4381,87,1,1). -snote(n45,[D,n],6,5:1,1/8,1/16,8.5000,8.7500,[v1,staff1])-note(n45,86,4410,4548,80,1,1). -snote(n46,[C,n],6,5:1,3/16,1/16,8.7500,9.0000,[v1,staff1])-note(n46,84,4531,4679,70,1,1). -snote(n47,[B,n],5,5:2,0,1/16,9.0000,9.2500,[v1,staff1])-note(n47,83,4637,4736,67,1,1). -snote(n48,[A,n],5,5:2,1/16,1/16,9.2500,9.5000,[v1,staff1])-note(n48,81,4730,4810,72,1,1). -snote(n49,[G,n],5,5:2,1/8,1/16,9.5000,9.7500,[v1,staff1])-note(n49,79,4838,4876,61,1,1). -snote(n50,[F,n],5,5:2,3/16,1/16,9.7500,10.0000,[v1,staff1])-note(n50,77,4939,4961,68,1,1). -snote(n53,[B,n],3,5:2,3/16,1/16,9.7500,10.0000,[v5,staff2])-note(n51,59,4944,4981,51,1,1). -snote(n54,[F,n],5,6:1,0,1/16,10.0000,10.2500,[v1,staff1])-note(n52,77,5062,5144,79,1,1). -snote(n62,[C,n],4,6:1,0,1/4,10.0000,11.0000,[v5,staff2])-note(n53,60,5099,5501,64,1,1). -snote(n55,[E,n],5,6:1,1/16,1/16,10.2500,10.5000,[v1,staff1])-note(n54,76,5176,5260,72,1,1). -snote(n56,[C,n],6,6:1,1/8,1/16,10.5000,10.7500,[v1,staff1])-note(n55,84,5303,5436,69,1,1). -snote(n57,[B,n],5,6:1,3/16,1/16,10.7500,11.0000,[v1,staff1])-note(n56,83,5407,5552,68,1,1). -snote(n58,[A,n],5,6:2,0,1/16,11.0000,11.2500,[v1,staff1])-note(n57,81,5509,5611,61,1,1). -snote(n59,[G,n],5,6:2,1/16,1/16,11.2500,11.5000,[v1,staff1])-note(n58,79,5596,5677,70,1,1). -snote(n60,[F,n],5,6:2,1/8,1/16,11.5000,11.7500,[v1,staff1])-note(n59,77,5684,5753,82,1,1). -snote(n61,[E,n],5,6:2,3/16,1/16,11.7500,12.0000,[v1,staff1])-note(n61,76,5809,5882,61,1,1). -snote(n64,[A,n],3,6:2,3/16,1/16,11.7500,12.0000,[v5,staff2])-note(n60,57,5793,5854,65,1,1). -snote(n65,[D,n],5,7:1,0,1/8,12.0000,12.5000,[v1,staff1])-note(n62,74,5922,6176,75,1,1). -snote(n69,[F,n],3,7:1,0,1/4,12.0000,13.0000,[v5,staff2])-note(n63,53,5978,6219,63,1,1). -snote(n66,[A,n],5,7:1,1/8,1/8,12.5000,13.0000,[v1,staff1])-note(n64,81,6197,6454,70,1,1). -snote(n67,[G,n],5,7:2,0,1/8,13.0000,13.5000,[v1,staff1])-note(n66,79,6420,6614,77,1,1). -snote(n70,[G,n],3,7:2,0,1/4,13.0000,14.0000,[v5,staff2])-note(n65,55,6416,6610,60,1,1). -snote(n68,[B,n],4,7:2,1/8,1/8,13.5000,14.0000,[v1,staff1])-note(n67,71,6643,6850,69,1,1). -snote(n71,[C,n],5,8:1,0,1/4,14.0000,15.0000,[v1,staff1])-note(n68,72,6898,7487,51,1,1). -snote(n73,[C,n],4,8:1,0,1/4,14.0000,15.0000,[v5,staff2])-note(n69,60,6911,7028,49,1,1). -snote(n74,[C,n],3,8:2,0,1/4,15.0000,16.0000,[v5,staff2])-note(n70,48,7434,7484,42,1,1). -snote(n75,[A,n],5,9:1,0,1/16,16.0000,16.2500,[v1,staff1])-note(n72,81,7928,7983,53,1,1). -snote(n83,[E,n],4,9:1,0,1/4,16.0000,17.0000,[v5,staff2])-note(n71,64,7920,7951,63,1,1). -snote(n76,[G,n],5,9:1,1/16,1/16,16.2500,16.5000,[v1,staff1])-note(n73,79,8012,8083,61,1,1). -snote(n77,[F,#],5,9:1,1/8,1/16,16.5000,16.7500,[v1,staff1])-note(n74,78,8126,8180,67,1,1). -snote(n78,[G,n],5,9:1,3/16,1/16,16.7500,17.0000,[v1,staff1])-note(n75,79,8258,8298,53,1,1). -snote(n79,[F,#],5,9:2,0,1/16,17.0000,17.2500,[v1,staff1])-note(n76,78,8358,8440,52,1,1). -snote(n84,[G,n],3,9:2,0,1/4,17.0000,18.0000,[v5,staff2])-note(n77,55,8362,8437,54,1,1). -snote(n80,[G,n],5,9:2,1/16,1/16,17.2500,17.5000,[v1,staff1])-note(n78,79,8459,8619,45,1,1). -snote(n81,[A,n],5,9:2,1/8,1/16,17.5000,17.7500,[v1,staff1])-note(n79,81,8565,8679,64,1,1). -snote(n82,[G,n],5,9:2,3/16,1/16,17.7500,18.0000,[v1,staff1])-note(n80,79,8673,8701,26,1,1). -snote(n85,[G,n],5,10:1,0,1/16,18.0000,18.2500,[v1,staff1])-note(n82,79,8812,8915,73,1,1). -snote(n93,[D,n],4,10:1,0,1/4,18.0000,19.0000,[v5,staff2])-note(n81,62,8806,8866,68,1,1). -snote(n86,[F,n],5,10:1,1/16,1/16,18.2500,18.5000,[v1,staff1])-note(n83,77,8931,8989,55,1,1). -snote(n87,[E,n],5,10:1,1/8,1/16,18.5000,18.7500,[v1,staff1])-note(n84,76,9002,9070,72,1,1). -snote(n88,[F,n],5,10:1,3/16,1/16,18.7500,19.0000,[v1,staff1])-note(n85,77,9126,9180,54,1,1). -snote(n89,[E,n],5,10:2,0,1/16,19.0000,19.2500,[v1,staff1])-note(n86,76,9206,9295,65,1,1). -snote(n94,[G,n],3,10:2,0,1/4,19.0000,20.0000,[v5,staff2])-note(n87,55,9256,9354,49,1,1). -snote(n90,[F,n],5,10:2,1/16,1/16,19.2500,19.5000,[v1,staff1])-note(n88,77,9325,9384,52,1,1). -snote(n91,[G,n],5,10:2,1/8,1/16,19.5000,19.7500,[v1,staff1])-note(n89,79,9415,9492,63,1,1). -snote(n92,[F,n],5,10:2,3/16,1/16,19.7500,20.0000,[v1,staff1])-note(n90,77,9500,9542,52,1,1). -snote(n95,[F,n],5,11:1,0,1/16,20.0000,20.2500,[v1,staff1])-note(n91,77,9649,9768,65,1,1). -snote(n103,[C,n],4,11:1,0,1/4,20.0000,21.0000,[v5,staff2])-note(n92,60,9665,9719,65,1,1). -snote(n96,[E,n],5,11:1,1/16,1/16,20.2500,20.5000,[v1,staff1])-note(n93,76,9763,9859,67,1,1). -snote(n97,[D,#],5,11:1,1/8,1/16,20.5000,20.7500,[v1,staff1])-note(n94,75,9870,9956,64,1,1). -snote(n98,[E,n],5,11:1,3/16,1/16,20.7500,21.0000,[v1,staff1])-note(n95,76,9988,10056,55,1,1). -snote(n99,[D,#],5,11:2,0,1/16,21.0000,21.2500,[v1,staff1])-note(n97,75,10090,10183,49,1,1). -snote(n104,[G,n],3,11:2,0,1/4,21.0000,22.0000,[v5,staff2])-note(n96,55,10085,10192,60,1,1). -snote(n100,[E,n],5,11:2,1/16,1/16,21.2500,21.5000,[v1,staff1])-note(n98,76,10205,10265,58,1,1). -snote(n101,[F,n],5,11:2,1/8,1/16,21.5000,21.7500,[v1,staff1])-note(n99,77,10313,10408,65,1,1). -snote(n102,[E,n],5,11:2,3/16,1/16,21.7500,22.0000,[v1,staff1])-note(n100,76,10394,10424,52,1,1). -snote(n105,[E,n],5,12:1,0,1/16,22.0000,22.2500,[v1,staff1])-note(n101,76,10527,10607,78,1,1). -snote(n113,[F,n],4,12:1,0,1/4,22.0000,23.0000,[v5,staff2])-note(n102,65,10540,10615,63,1,1). -snote(n106,[D,n],5,12:1,1/16,1/16,22.2500,22.5000,[v1,staff1])-note(n103,74,10646,10711,58,1,1). -snote(n107,[C,#],5,12:1,1/8,1/16,22.5000,22.7500,[v1,staff1])-note(n104,73,10755,10818,55,1,1). -snote(n108,[D,n],5,12:1,3/16,1/16,22.7500,23.0000,[v1,staff1])-note(n105,74,10870,10910,48,1,1). -snote(n109,[C,#],5,12:2,0,1/16,23.0000,23.2500,[v1,staff1])-note(n106,73,10958,11052,55,1,1). -snote(n114,[G,n],3,12:2,0,1/4,23.0000,24.0000,[v5,staff2])-note(n107,55,10993,11084,49,1,1). -snote(n110,[D,n],5,12:2,1/16,1/16,23.2500,23.5000,[v1,staff1])-note(n108,74,11095,11115,39,1,1). -snote(n111,[E,n],5,12:2,1/8,1/16,23.5000,23.7500,[v1,staff1])-note(n109,76,11161,11217,55,1,1). -snote(n112,[D,n],5,12:2,3/16,1/16,23.7500,24.0000,[v1,staff1])-note(n110,74,11233,11283,61,1,1). -snote(n115,[A,n],5,13:1,0,1/16,24.0000,24.2500,[v1,staff1])-note(n113,81,11420,11494,67,1,1). -snote(n123,[G,n],3,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n112,55,11406,12019,53,1,1). -snote(n124,[E,n],4,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n111,64,11400,12020,60,1,1). -snote(n116,[G,n],5,13:1,1/16,1/16,24.2500,24.5000,[v1,staff1])-note(n114,79,11536,11590,66,1,1). -snote(n117,[F,#],5,13:1,1/8,1/16,24.5000,24.7500,[v1,staff1])-note(n115,78,11643,11701,52,1,1). -snote(n118,[G,n],5,13:1,3/16,1/16,24.7500,25.0000,[v1,staff1])-note(n116,79,11731,11808,69,1,1). -snote(n119,[E,n],6,13:2,0,1/16,25.0000,25.2500,[v1,staff1])-note(n117,88,11851,11976,77,1,1). -snote(n120,[C,n],6,13:2,1/16,1/16,25.2500,25.5000,[v1,staff1])-note(n118,84,11963,12107,58,1,1). -snote(n121,[A,n],5,13:2,1/8,1/16,25.5000,25.7500,[v1,staff1])-note(n119,81,12068,12150,66,1,1). -snote(n122,[G,n],5,13:2,3/16,1/16,25.7500,26.0000,[v1,staff1])-note(n120,79,12149,12197,64,1,1). -snote(n125,[G,n],5,14:1,0,1/16,26.0000,26.2500,[v1,staff1])-note(n123,79,12315,12393,75,1,1). -snote(n133,[G,n],3,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n121,55,12304,12924,54,1,1). -snote(n134,[D,n],4,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n122,62,12313,12949,61,1,1). -snote(n126,[F,n],5,14:1,1/16,1/16,26.2500,26.5000,[v1,staff1])-note(n124,77,12425,12501,73,1,1). -snote(n127,[E,n],5,14:1,1/8,1/16,26.5000,26.7500,[v1,staff1])-note(n125,76,12548,12614,53,1,1). -snote(n128,[F,n],5,14:1,3/16,1/16,26.7500,27.0000,[v1,staff1])-note(n126,77,12643,12723,77,1,1). -snote(n129,[D,n],6,14:2,0,1/16,27.0000,27.2500,[v1,staff1])-note(n127,86,12762,12910,79,1,1). -snote(n130,[B,n],5,14:2,1/16,1/16,27.2500,27.5000,[v1,staff1])-note(n128,83,12884,13010,59,1,1). -snote(n131,[G,n],5,14:2,1/8,1/16,27.5000,27.7500,[v1,staff1])-note(n129,79,12988,13049,64,1,1). -snote(n132,[F,n],5,14:2,3/16,1/16,27.7500,28.0000,[v1,staff1])-note(n130,77,13061,13104,62,1,1). -snote(n135,[F,n],5,15:1,0,1/16,28.0000,28.2500,[v1,staff1])-note(n131,77,13215,13299,64,1,1). -snote(n143,[G,n],3,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n132,55,13227,13754,45,1,1). -snote(n144,[C,n],4,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n133,60,13229,13761,42,1,1). -snote(n136,[E,n],5,15:1,1/16,1/16,28.2500,28.5000,[v1,staff1])-note(n134,76,13343,13394,52,1,1). -snote(n137,[D,#],5,15:1,1/8,1/16,28.5000,28.7500,[v1,staff1])-note(n135,75,13449,13547,61,1,1). -snote(n138,[E,n],5,15:1,3/16,1/16,28.7500,29.0000,[v1,staff1])-note(n136,76,13570,13709,58,1,1). -snote(n139,[C,n],6,15:2,0,1/16,29.0000,29.2500,[v1,staff1])-note(n137,84,13666,13790,67,1,1). -insertion-note(n138,81,13852,13931,23,1,1). -snote(n140,[G,n],5,15:2,1/16,1/16,29.2500,29.5000,[v1,staff1])-note(n139,79,13854,13892,31,1,1). -snote(n141,[F,n],5,15:2,1/8,1/16,29.5000,29.7500,[v1,staff1])-note(n140,77,13900,13997,55,1,1). -snote(n142,[E,n],5,15:2,3/16,1/16,29.7500,30.0000,[v1,staff1])-note(n142,76,14007,14217,62,1,1). -snote(n146,[C,n],4,15:2,3/16,1/16,29.7500,30.0000,[v5,staff2])-note(n141,60,13990,14130,52,1,1). -snote(n147,[G,n],5,16:1,0,3/16,30.0000,30.7500,[v1,staff1])-note(n145,79,14219,14738,68,1,1). -snote(n150,[E,n],4,16:1,0,3/16,30.0000,30.7500,[v5,staff2])-note(n144,64,14198,14709,44,1,1). -snote(n153,[G,n],3,16:1,0,1/2,30.0000,32.0000,[v6,staff2])-note(n143,55,14181,14897,45,1,1). -snote(n148,[E,n],5,16:1,3/16,1/16,30.7500,31.0000,[v1,staff1])-note(n147,76,14733,14901,47,1,1). -snote(n151,[C,n],4,16:1,3/16,1/16,30.7500,31.0000,[v5,staff2])-note(n146,60,14730,14872,43,1,1). -snote(n149,[D,n],5,16:2,0,1/4,31.0000,32.0000,[v1,staff1])-note(n149,74,14894,15140,44,1,1). -snote(n152,[B,n],3,16:2,0,1/4,31.0000,32.0000,[v5,staff2])-note(n148,59,14892,15103,51,1,1). -snote(n154,[D,n],5,17:1,0,1/16,32.0000,32.2500,[v1,staff1])-note(n151,74,15590,15672,58,1,1). -snote(n162,[C,n],3,17:1,0,1/4,32.0000,33.0000,[v5,staff2])-note(n150,48,15585,15661,61,1,1). -snote(n155,[C,n],5,17:1,1/16,1/16,32.2500,32.5000,[v1,staff1])-note(n152,72,15720,15756,39,1,1). -snote(n156,[B,n],4,17:1,1/8,1/16,32.5000,32.7500,[v1,staff1])-note(n153,71,15800,15856,54,1,1). -snote(n157,[C,n],5,17:1,3/16,1/16,32.7500,33.0000,[v1,staff1])-note(n154,72,15895,15993,55,1,1). -snote(n158,[B,n],4,17:2,0,1/16,33.0000,33.2500,[v1,staff1])-note(n156,71,16026,16096,48,1,1). -snote(n163,[C,n],4,17:2,0,1/4,33.0000,34.0000,[v5,staff2])-note(n155,60,16014,16068,59,1,1). -snote(n159,[C,n],5,17:2,1/16,1/16,33.2500,33.5000,[v1,staff1])-note(n157,72,16170,16214,43,1,1). -snote(n160,[B,n],4,17:2,1/8,1/16,33.5000,33.7500,[v1,staff1])-note(n158,71,16255,16315,59,1,1). -snote(n161,[C,n],5,17:2,3/16,1/16,33.7500,34.0000,[v1,staff1])-note(n159,72,16355,16377,49,1,1). -snote(n164,[A,n],5,18:1,0,1/16,34.0000,34.2500,[v1,staff1])-note(n161,81,16460,16530,25,1,1). -snote(n172,[E,n],4,18:1,0,1/4,34.0000,35.0000,[v5,staff2])-note(n160,64,16437,16500,59,1,1). -snote(n165,[G,n],5,18:1,1/16,1/16,34.2500,34.5000,[v1,staff1])-note(n162,79,16576,16647,63,1,1). -snote(n166,[F,#],5,18:1,1/8,1/16,34.5000,34.7500,[v1,staff1])-note(n163,78,16674,16734,58,1,1). -snote(n167,[G,n],5,18:1,3/16,1/16,34.7500,35.0000,[v1,staff1])-note(n164,79,16802,16826,42,1,1). -snote(n168,[F,#],5,18:2,0,1/16,35.0000,35.2500,[v1,staff1])-note(n165,78,16897,16950,54,1,1). -snote(n173,[C,n],4,18:2,0,1/4,35.0000,36.0000,[v5,staff2])-note(n166,60,16900,16961,47,1,1). -snote(n169,[G,n],5,18:2,1/16,1/16,35.2500,35.5000,[v1,staff1])-note(n167,79,17000,17030,44,1,1). -snote(n170,[F,#],5,18:2,1/8,1/16,35.5000,35.7500,[v1,staff1])-note(n168,78,17095,17149,55,1,1). -snote(n171,[G,n],5,18:2,3/16,1/16,35.7500,36.0000,[v1,staff1])-note(n169,79,17227,17239,32,1,1). -snote(n174,[G,#],5,19:1,0,1/16,36.0000,36.2500,[v1,staff1])-note(n170,80,17291,17379,60,1,1). -snote(n182,[F,n],4,19:1,0,1/4,36.0000,37.0000,[v5,staff2])-note(n171,65,17318,17379,50,1,1). -snote(n175,[A,n],5,19:1,1/16,1/16,36.2500,36.5000,[v1,staff1])-note(n172,81,17413,17516,64,1,1). -snote(n176,[C,n],6,19:1,1/8,1/16,36.5000,36.7500,[v1,staff1])-note(n173,84,17515,17625,60,1,1). -snote(n177,[B,n],5,19:1,3/16,1/16,36.7500,37.0000,[v1,staff1])-note(n174,83,17639,17715,44,1,1). -snote(n178,[D,n],6,19:2,0,1/16,37.0000,37.2500,[v1,staff1])-note(n175,86,17721,17846,61,1,1). -snote(n183,[C,n],4,19:2,0,1/4,37.0000,38.0000,[v5,staff2])-note(n176,60,17737,17822,46,1,1). -snote(n179,[C,n],6,19:2,1/16,1/16,37.2500,37.5000,[v1,staff1])-note(n177,84,17871,17948,42,1,1). -snote(n180,[B,n],5,19:2,1/8,1/16,37.5000,37.7500,[v1,staff1])-note(n178,83,17953,18033,55,1,1). -snote(n181,[A,n],5,19:2,3/16,1/16,37.7500,38.0000,[v1,staff1])-note(n179,81,18035,18067,58,1,1). -snote(n184,[A,n],5,20:1,0,1/16,38.0000,38.2500,[v1,staff1])-note(n182,81,18201,18252,55,1,1). -snote(n192,[C,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n180,60,18194,18582,48,1,1). -snote(n193,[E,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n181,64,18201,18543,55,1,1). -snote(n185,[G,n],5,20:1,1/16,1/16,38.2500,38.5000,[v1,staff1])-note(n183,79,18290,18385,67,1,1). -snote(n186,[E,n],6,20:1,1/8,1/16,38.5000,38.7500,[v1,staff1])-note(n184,88,18420,18561,66,1,1). -snote(n187,[D,n],6,20:1,3/16,1/16,38.7500,39.0000,[v1,staff1])-note(n185,86,18532,18660,63,1,1). -snote(n188,[C,n],6,20:2,0,1/16,39.0000,39.2500,[v1,staff1])-note(n186,84,18632,18742,71,1,1). -snote(n189,[B,n],5,20:2,1/16,1/16,39.2500,39.5000,[v1,staff1])-note(n187,83,18721,18794,68,1,1). -snote(n190,[A,n],5,20:2,1/8,1/16,39.5000,39.7500,[v1,staff1])-note(n188,81,18813,18856,60,1,1). -snote(n191,[G,n],5,20:2,3/16,1/16,39.7500,40.0000,[v1,staff1])-note(n190,79,18948,18977,71,1,1). -snote(n195,[C,#],4,20:2,3/16,1/16,39.7500,40.0000,[v5,staff2])-note(n189,61,18940,18986,58,1,1). -snote(n196,[G,n],5,21:1,0,1/16,40.0000,40.2500,[v1,staff1])-note(n191,79,19065,19157,77,1,1). -snote(n204,[D,n],4,21:1,0,1/4,40.0000,41.0000,[v5,staff2])-note(n192,62,19087,19522,74,1,1). -snote(n197,[F,n],5,21:1,1/16,1/16,40.2500,40.5000,[v1,staff1])-note(n193,77,19185,19289,84,1,1). -snote(n198,[D,n],6,21:1,1/8,1/16,40.5000,40.7500,[v1,staff1])-note(n194,86,19293,19411,75,1,1). -snote(n199,[C,n],6,21:1,3/16,1/16,40.7500,41.0000,[v1,staff1])-note(n195,84,19409,19543,62,1,1). -snote(n200,[B,n],5,21:2,0,1/16,41.0000,41.2500,[v1,staff1])-note(n196,83,19506,19618,71,1,1). -snote(n201,[A,n],5,21:2,1/16,1/16,41.2500,41.5000,[v1,staff1])-note(n197,81,19616,19671,45,1,1). -snote(n202,[G,n],5,21:2,1/8,1/16,41.5000,41.7500,[v1,staff1])-note(n198,79,19679,19734,70,1,1). -snote(n203,[F,n],5,21:2,3/16,1/16,41.7500,42.0000,[v1,staff1])-note(n199,77,19810,19845,81,1,1). -snote(n206,[B,n],3,21:2,3/16,1/16,41.7500,42.0000,[v5,staff2])-note(n200,59,19840,19876,47,1,1). -snote(n207,[F,n],5,22:1,0,1/16,42.0000,42.2500,[v1,staff1])-note(n201,77,19948,20032,77,1,1). -snote(n215,[C,n],4,22:1,0,1/4,42.0000,43.0000,[v5,staff2])-note(n202,60,19981,20353,69,1,1). -snote(n208,[E,n],5,22:1,1/16,1/16,42.2500,42.5000,[v1,staff1])-note(n203,76,20053,20156,77,1,1). -snote(n209,[C,n],6,22:1,1/8,1/16,42.5000,42.7500,[v1,staff1])-note(n204,84,20176,20270,68,1,1). -snote(n210,[B,n],5,22:1,3/16,1/16,42.7500,43.0000,[v1,staff1])-note(n205,83,20276,20417,62,1,1). -snote(n211,[A,n],5,22:2,0,1/16,43.0000,43.2500,[v1,staff1])-note(n206,81,20379,20485,59,1,1). -snote(n212,[G,n],5,22:2,1/16,1/16,43.2500,43.5000,[v1,staff1])-note(n207,79,20462,20534,71,1,1). -snote(n213,[F,n],5,22:2,1/8,1/16,43.5000,43.7500,[v1,staff1])-note(n208,77,20557,20635,72,1,1). -snote(n214,[E,n],5,22:2,3/16,1/16,43.7500,44.0000,[v1,staff1])-note(n209,76,20663,20743,76,1,1). -snote(n217,[A,n],3,22:2,3/16,1/16,43.7500,44.0000,[v5,staff2])-note(n210,57,20675,20787,61,1,1). -snote(n218,[D,n],5,23:1,0,1/8,44.0000,44.5000,[v1,staff1])-note(n211,74,20796,21067,64,1,1). -snote(n222,[F,n],3,23:1,0,1/4,44.0000,45.0000,[v5,staff2])-note(n212,53,20854,21113,72,1,1). -snote(n219,[A,n],5,23:1,1/8,1/8,44.5000,45.0000,[v1,staff1])-note(n213,81,21064,21476,68,1,1). -snote(n220,[G,n],5,23:2,0,1/8,45.0000,45.5000,[v1,staff1])-note(n215,79,21296,21543,75,1,1). -snote(n223,[G,n],3,23:2,0,1/4,45.0000,46.0000,[v5,staff2])-note(n214,55,21287,21555,70,1,1). -snote(n221,[B,n],4,23:2,1/8,1/8,45.5000,46.0000,[v1,staff1])-note(n216,71,21540,21748,58,1,1). -snote(n224,[C,n],5,24:1,0,1/4,46.0000,47.0000,[v1,staff1])-note(n217,72,21793,22915,48,1,1). -snote(n226,[C,n],4,24:1,0,1/4,46.0000,47.0000,[v5,staff2])-note(n218,60,21819,22029,49,1,1). -snote(n227,[C,n],3,24:2,0,1/4,47.0000,48.0000,[v5,staff2])-note(n219,48,22393,22966,50,1,1). +snote(n1,[D,n],5,1:1,0,1/16,0.0000,0.2500,[v1,staff1])-note(n1,74,692,773,72,1,1). +snote(n9,[C,n],3,1:1,0,1/4,0.0000,1.0000,[v5,staff2])-note(n0,48,683,747,70,1,1). +snote(n2,[C,n],5,1:1,1/16,1/16,0.2500,0.5000,[v1,staff1])-note(n2,72,819,892,53,1,1). +snote(n3,[B,n],4,1:1,1/8,1/16,0.5000,0.7500,[v1,staff1])-note(n3,71,914,980,54,1,1). +snote(n4,[C,n],5,1:1,3/16,1/16,0.7500,1.0000,[v1,staff1])-note(n4,72,1009,1098,48,1,1). +snote(n5,[B,n],4,1:2,0,1/16,1.0000,1.2500,[v1,staff1])-note(n6,71,1128,1174,61,1,1). +snote(n10,[C,n],4,1:2,0,1/4,1.0000,2.0000,[v5,staff2])-note(n5,60,1122,1160,67,1,1). +snote(n6,[C,n],5,1:2,1/16,1/16,1.2500,1.5000,[v1,staff1])-note(n7,72,1233,1284,44,1,1). +snote(n7,[B,n],4,1:2,1/8,1/16,1.5000,1.7500,[v1,staff1])-note(n8,71,1316,1410,65,1,1). +snote(n8,[C,n],5,1:2,3/16,1/16,1.7500,2.0000,[v1,staff1])-note(n9,72,1420,1490,61,1,1). +snote(n11,[A,n],5,2:1,0,1/16,2.0000,2.2500,[v1,staff1])-note(n11,81,1556,1637,55,1,1). +snote(n19,[E,n],4,2:1,0,1/4,2.0000,3.0000,[v5,staff2])-note(n10,64,1541,1614,75,1,1). +snote(n12,[G,n],5,2:1,1/16,1/16,2.2500,2.5000,[v1,staff1])-note(n12,79,1683,1752,62,1,1). +snote(n13,[F,#],5,2:1,1/8,1/16,2.5000,2.7500,[v1,staff1])-note(n13,78,1785,1831,52,1,1). +snote(n14,[G,n],5,2:1,3/16,1/16,2.7500,3.0000,[v1,staff1])-note(n14,79,1893,1949,77,1,1). +snote(n15,[F,#],5,2:2,0,1/16,3.0000,3.2500,[v1,staff1])-note(n15,78,1984,2041,60,1,1). +snote(n20,[C,n],4,2:2,0,1/4,3.0000,4.0000,[v5,staff2])-note(n16,60,2002,2043,49,1,1). +snote(n16,[G,n],5,2:2,1/16,1/16,3.2500,3.5000,[v1,staff1])-note(n17,79,2100,2130,52,1,1). +snote(n17,[F,#],5,2:2,1/8,1/16,3.5000,3.7500,[v1,staff1])-note(n18,78,2180,2235,64,1,1). +snote(n18,[G,n],5,2:2,3/16,1/16,3.7500,4.0000,[v1,staff1])-note(n19,79,2275,2317,54,1,1). +snote(n21,[G,#],5,3:1,0,1/16,4.0000,4.2500,[v1,staff1])-note(n20,80,2392,2454,61,1,1). +snote(n29,[F,n],4,3:1,0,1/4,4.0000,5.0000,[v5,staff2])-note(n21,65,2416,2457,61,1,1). +snote(n22,[A,n],5,3:1,1/16,1/16,4.2500,4.5000,[v1,staff1])-note(n22,81,2495,2600,61,1,1). +snote(n23,[C,n],6,3:1,1/8,1/16,4.5000,4.7500,[v1,staff1])-note(n23,84,2611,2722,62,1,1). +snote(n24,[B,n],5,3:1,3/16,1/16,4.7500,5.0000,[v1,staff1])-note(n24,83,2722,2796,51,1,1). +snote(n25,[D,n],6,3:2,0,1/16,5.0000,5.2500,[v1,staff1])-note(n25,86,2807,2939,61,1,1). +snote(n30,[C,n],4,3:2,0,1/4,5.0000,6.0000,[v5,staff2])-note(n26,60,2836,2890,45,1,1). +snote(n26,[C,n],6,3:2,1/16,1/16,5.2500,5.5000,[v1,staff1])-note(n27,84,2939,3024,55,1,1). +snote(n27,[B,n],5,3:2,1/8,1/16,5.5000,5.7500,[v1,staff1])-note(n28,83,3033,3093,64,1,1). +snote(n28,[A,n],5,3:2,3/16,1/16,5.7500,6.0000,[v1,staff1])-note(n29,81,3138,3162,52,1,1). +snote(n31,[A,n],5,4:1,0,1/16,6.0000,6.2500,[v1,staff1])-note(n31,81,3280,3364,68,1,1). +snote(n39,[C,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n30,60,3275,3538,42,1,1). +snote(n40,[E,n],4,4:1,0,1/4,6.0000,7.0000,[v5,staff2])-note(n32,64,3291,3526,59,1,1). +snote(n32,[G,n],5,4:1,1/16,1/16,6.2500,6.5000,[v1,staff1])-note(n33,79,3413,3499,62,1,1). +snote(n33,[E,n],6,4:1,1/8,1/16,6.5000,6.7500,[v1,staff1])-note(n34,88,3531,3657,69,1,1). +snote(n34,[D,n],6,4:1,3/16,1/16,6.7500,7.0000,[v1,staff1])-note(n35,86,3639,3762,74,1,1). +snote(n35,[C,n],6,4:2,0,1/16,7.0000,7.2500,[v1,staff1])-note(n36,84,3758,3859,62,1,1). +snote(n36,[B,n],5,4:2,1/16,1/16,7.2500,7.5000,[v1,staff1])-note(n37,83,3845,3912,64,1,1). +snote(n37,[A,n],5,4:2,1/8,1/16,7.5000,7.7500,[v1,staff1])-note(n38,81,3933,3976,59,1,1). +snote(n38,[G,n],5,4:2,3/16,1/16,7.7500,8.0000,[v1,staff1])-note(n40,79,4058,4069,58,1,1). +snote(n42,[C,#],4,4:2,3/16,1/16,7.7500,8.0000,[v5,staff2])-note(n39,61,4050,4094,60,1,1). +snote(n43,[G,n],5,5:1,0,1/16,8.0000,8.2500,[v1,staff1])-note(n41,79,4187,4262,63,1,1). +snote(n51,[D,n],4,5:1,0,1/4,8.0000,9.0000,[v5,staff2])-note(n42,62,4203,4563,79,1,1). +snote(n44,[F,n],5,5:1,1/16,1/16,8.2500,8.5000,[v1,staff1])-note(n43,77,4289,4381,87,1,1). +snote(n45,[D,n],6,5:1,1/8,1/16,8.5000,8.7500,[v1,staff1])-note(n44,86,4410,4548,80,1,1). +snote(n46,[C,n],6,5:1,3/16,1/16,8.7500,9.0000,[v1,staff1])-note(n45,84,4531,4679,70,1,1). +snote(n47,[B,n],5,5:2,0,1/16,9.0000,9.2500,[v1,staff1])-note(n46,83,4637,4736,67,1,1). +snote(n48,[A,n],5,5:2,1/16,1/16,9.2500,9.5000,[v1,staff1])-note(n47,81,4730,4810,72,1,1). +snote(n49,[G,n],5,5:2,1/8,1/16,9.5000,9.7500,[v1,staff1])-note(n48,79,4838,4876,61,1,1). +snote(n50,[F,n],5,5:2,3/16,1/16,9.7500,10.0000,[v1,staff1])-note(n49,77,4939,4961,68,1,1). +snote(n53,[B,n],3,5:2,3/16,1/16,9.7500,10.0000,[v5,staff2])-note(n50,59,4944,4981,51,1,1). +snote(n54,[F,n],5,6:1,0,1/16,10.0000,10.2500,[v1,staff1])-note(n51,77,5062,5144,79,1,1). +snote(n62,[C,n],4,6:1,0,1/4,10.0000,11.0000,[v5,staff2])-note(n52,60,5099,5501,64,1,1). +snote(n55,[E,n],5,6:1,1/16,1/16,10.2500,10.5000,[v1,staff1])-note(n53,76,5176,5260,72,1,1). +snote(n56,[C,n],6,6:1,1/8,1/16,10.5000,10.7500,[v1,staff1])-note(n54,84,5303,5436,69,1,1). +snote(n57,[B,n],5,6:1,3/16,1/16,10.7500,11.0000,[v1,staff1])-note(n55,83,5407,5552,68,1,1). +snote(n58,[A,n],5,6:2,0,1/16,11.0000,11.2500,[v1,staff1])-note(n56,81,5509,5611,61,1,1). +snote(n59,[G,n],5,6:2,1/16,1/16,11.2500,11.5000,[v1,staff1])-note(n57,79,5596,5677,70,1,1). +snote(n60,[F,n],5,6:2,1/8,1/16,11.5000,11.7500,[v1,staff1])-note(n58,77,5684,5753,82,1,1). +snote(n61,[E,n],5,6:2,3/16,1/16,11.7500,12.0000,[v1,staff1])-note(n60,76,5809,5882,61,1,1). +snote(n64,[A,n],3,6:2,3/16,1/16,11.7500,12.0000,[v5,staff2])-note(n59,57,5793,5854,65,1,1). +snote(n65,[D,n],5,7:1,0,1/8,12.0000,12.5000,[v1,staff1])-note(n61,74,5922,6176,75,1,1). +snote(n69,[F,n],3,7:1,0,1/4,12.0000,13.0000,[v5,staff2])-note(n62,53,5978,6219,63,1,1). +snote(n66,[A,n],5,7:1,1/8,1/8,12.5000,13.0000,[v1,staff1])-note(n63,81,6197,6454,70,1,1). +snote(n67,[G,n],5,7:2,0,1/8,13.0000,13.5000,[v1,staff1])-note(n65,79,6420,6614,77,1,1). +snote(n70,[G,n],3,7:2,0,1/4,13.0000,14.0000,[v5,staff2])-note(n64,55,6416,6610,60,1,1). +snote(n68,[B,n],4,7:2,1/8,1/8,13.5000,14.0000,[v1,staff1])-note(n66,71,6643,6850,69,1,1). +snote(n71,[C,n],5,8:1,0,1/4,14.0000,15.0000,[v1,staff1])-note(n67,72,6898,7487,51,1,1). +snote(n73,[C,n],4,8:1,0,1/4,14.0000,15.0000,[v5,staff2])-note(n68,60,6911,7028,49,1,1). +snote(n74,[C,n],3,8:2,0,1/4,15.0000,16.0000,[v5,staff2])-note(n69,48,7434,7484,42,1,1). +snote(n75,[A,n],5,9:1,0,1/16,16.0000,16.2500,[v1,staff1])-note(n71,81,7928,7983,53,1,1). +snote(n83,[E,n],4,9:1,0,1/4,16.0000,17.0000,[v5,staff2])-note(n70,64,7920,7951,63,1,1). +snote(n76,[G,n],5,9:1,1/16,1/16,16.2500,16.5000,[v1,staff1])-note(n72,79,8012,8083,61,1,1). +snote(n77,[F,#],5,9:1,1/8,1/16,16.5000,16.7500,[v1,staff1])-note(n73,78,8126,8180,67,1,1). +snote(n78,[G,n],5,9:1,3/16,1/16,16.7500,17.0000,[v1,staff1])-note(n74,79,8258,8298,53,1,1). +snote(n79,[F,#],5,9:2,0,1/16,17.0000,17.2500,[v1,staff1])-note(n75,78,8358,8440,52,1,1). +snote(n84,[G,n],3,9:2,0,1/4,17.0000,18.0000,[v5,staff2])-note(n76,55,8362,8437,54,1,1). +snote(n80,[G,n],5,9:2,1/16,1/16,17.2500,17.5000,[v1,staff1])-note(n77,79,8459,8619,45,1,1). +snote(n81,[A,n],5,9:2,1/8,1/16,17.5000,17.7500,[v1,staff1])-note(n78,81,8565,8679,64,1,1). +snote(n82,[G,n],5,9:2,3/16,1/16,17.7500,18.0000,[v1,staff1])-note(n79,79,8673,8701,26,1,1). +snote(n85,[G,n],5,10:1,0,1/16,18.0000,18.2500,[v1,staff1])-note(n81,79,8812,8915,73,1,1). +snote(n93,[D,n],4,10:1,0,1/4,18.0000,19.0000,[v5,staff2])-note(n80,62,8806,8866,68,1,1). +snote(n86,[F,n],5,10:1,1/16,1/16,18.2500,18.5000,[v1,staff1])-note(n82,77,8931,8989,55,1,1). +snote(n87,[E,n],5,10:1,1/8,1/16,18.5000,18.7500,[v1,staff1])-note(n83,76,9002,9070,72,1,1). +snote(n88,[F,n],5,10:1,3/16,1/16,18.7500,19.0000,[v1,staff1])-note(n84,77,9126,9180,54,1,1). +snote(n89,[E,n],5,10:2,0,1/16,19.0000,19.2500,[v1,staff1])-note(n85,76,9206,9295,65,1,1). +snote(n94,[G,n],3,10:2,0,1/4,19.0000,20.0000,[v5,staff2])-note(n86,55,9256,9354,49,1,1). +snote(n90,[F,n],5,10:2,1/16,1/16,19.2500,19.5000,[v1,staff1])-note(n87,77,9325,9384,52,1,1). +snote(n91,[G,n],5,10:2,1/8,1/16,19.5000,19.7500,[v1,staff1])-note(n88,79,9415,9492,63,1,1). +snote(n92,[F,n],5,10:2,3/16,1/16,19.7500,20.0000,[v1,staff1])-note(n89,77,9500,9542,52,1,1). +snote(n95,[F,n],5,11:1,0,1/16,20.0000,20.2500,[v1,staff1])-note(n90,77,9649,9768,65,1,1). +snote(n103,[C,n],4,11:1,0,1/4,20.0000,21.0000,[v5,staff2])-note(n91,60,9665,9719,65,1,1). +snote(n96,[E,n],5,11:1,1/16,1/16,20.2500,20.5000,[v1,staff1])-note(n92,76,9763,9859,67,1,1). +snote(n97,[D,#],5,11:1,1/8,1/16,20.5000,20.7500,[v1,staff1])-note(n93,75,9870,9956,64,1,1). +snote(n98,[E,n],5,11:1,3/16,1/16,20.7500,21.0000,[v1,staff1])-note(n94,76,9988,10056,55,1,1). +snote(n99,[D,#],5,11:2,0,1/16,21.0000,21.2500,[v1,staff1])-note(n96,75,10090,10183,49,1,1). +snote(n104,[G,n],3,11:2,0,1/4,21.0000,22.0000,[v5,staff2])-note(n95,55,10085,10192,60,1,1). +snote(n100,[E,n],5,11:2,1/16,1/16,21.2500,21.5000,[v1,staff1])-note(n97,76,10205,10265,58,1,1). +snote(n101,[F,n],5,11:2,1/8,1/16,21.5000,21.7500,[v1,staff1])-note(n98,77,10313,10408,65,1,1). +snote(n102,[E,n],5,11:2,3/16,1/16,21.7500,22.0000,[v1,staff1])-note(n99,76,10394,10424,52,1,1). +snote(n105,[E,n],5,12:1,0,1/16,22.0000,22.2500,[v1,staff1])-note(n100,76,10527,10607,78,1,1). +snote(n113,[F,n],4,12:1,0,1/4,22.0000,23.0000,[v5,staff2])-note(n101,65,10540,10615,63,1,1). +snote(n106,[D,n],5,12:1,1/16,1/16,22.2500,22.5000,[v1,staff1])-note(n102,74,10646,10711,58,1,1). +snote(n107,[C,#],5,12:1,1/8,1/16,22.5000,22.7500,[v1,staff1])-note(n103,73,10755,10818,55,1,1). +snote(n108,[D,n],5,12:1,3/16,1/16,22.7500,23.0000,[v1,staff1])-note(n104,74,10870,10910,48,1,1). +snote(n109,[C,#],5,12:2,0,1/16,23.0000,23.2500,[v1,staff1])-note(n105,73,10958,11052,55,1,1). +snote(n114,[G,n],3,12:2,0,1/4,23.0000,24.0000,[v5,staff2])-note(n106,55,10993,11084,49,1,1). +snote(n110,[D,n],5,12:2,1/16,1/16,23.2500,23.5000,[v1,staff1])-note(n107,74,11095,11115,39,1,1). +snote(n111,[E,n],5,12:2,1/8,1/16,23.5000,23.7500,[v1,staff1])-note(n108,76,11161,11217,55,1,1). +snote(n112,[D,n],5,12:2,3/16,1/16,23.7500,24.0000,[v1,staff1])-note(n109,74,11233,11283,61,1,1). +snote(n115,[A,n],5,13:1,0,1/16,24.0000,24.2500,[v1,staff1])-note(n112,81,11420,11494,67,1,1). +snote(n123,[G,n],3,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n111,55,11406,12019,53,1,1). +snote(n124,[E,n],4,13:1,0,1/2,24.0000,26.0000,[v5,staff2])-note(n110,64,11400,12020,60,1,1). +snote(n116,[G,n],5,13:1,1/16,1/16,24.2500,24.5000,[v1,staff1])-note(n113,79,11536,11590,66,1,1). +snote(n117,[F,#],5,13:1,1/8,1/16,24.5000,24.7500,[v1,staff1])-note(n114,78,11643,11701,52,1,1). +snote(n118,[G,n],5,13:1,3/16,1/16,24.7500,25.0000,[v1,staff1])-note(n115,79,11731,11808,69,1,1). +snote(n119,[E,n],6,13:2,0,1/16,25.0000,25.2500,[v1,staff1])-note(n116,88,11851,11976,77,1,1). +snote(n120,[C,n],6,13:2,1/16,1/16,25.2500,25.5000,[v1,staff1])-note(n117,84,11963,12107,58,1,1). +snote(n121,[A,n],5,13:2,1/8,1/16,25.5000,25.7500,[v1,staff1])-note(n118,81,12068,12150,66,1,1). +snote(n122,[G,n],5,13:2,3/16,1/16,25.7500,26.0000,[v1,staff1])-note(n119,79,12149,12197,64,1,1). +snote(n125,[G,n],5,14:1,0,1/16,26.0000,26.2500,[v1,staff1])-note(n122,79,12315,12393,75,1,1). +snote(n133,[G,n],3,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n120,55,12304,12924,54,1,1). +snote(n134,[D,n],4,14:1,0,1/2,26.0000,28.0000,[v5,staff2])-note(n121,62,12313,12949,61,1,1). +snote(n126,[F,n],5,14:1,1/16,1/16,26.2500,26.5000,[v1,staff1])-note(n123,77,12425,12501,73,1,1). +snote(n127,[E,n],5,14:1,1/8,1/16,26.5000,26.7500,[v1,staff1])-note(n124,76,12548,12614,53,1,1). +snote(n128,[F,n],5,14:1,3/16,1/16,26.7500,27.0000,[v1,staff1])-note(n125,77,12643,12723,77,1,1). +snote(n129,[D,n],6,14:2,0,1/16,27.0000,27.2500,[v1,staff1])-note(n126,86,12762,12910,79,1,1). +snote(n130,[B,n],5,14:2,1/16,1/16,27.2500,27.5000,[v1,staff1])-note(n127,83,12884,13010,59,1,1). +snote(n131,[G,n],5,14:2,1/8,1/16,27.5000,27.7500,[v1,staff1])-note(n128,79,12988,13049,64,1,1). +snote(n132,[F,n],5,14:2,3/16,1/16,27.7500,28.0000,[v1,staff1])-note(n129,77,13061,13104,62,1,1). +snote(n135,[F,n],5,15:1,0,1/16,28.0000,28.2500,[v1,staff1])-note(n130,77,13215,13299,64,1,1). +snote(n143,[G,n],3,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n131,55,13227,13754,45,1,1). +snote(n144,[C,n],4,15:1,0,1/4,28.0000,29.0000,[v5,staff2])-note(n132,60,13229,13761,42,1,1). +snote(n136,[E,n],5,15:1,1/16,1/16,28.2500,28.5000,[v1,staff1])-note(n133,76,13343,13394,52,1,1). +snote(n137,[D,#],5,15:1,1/8,1/16,28.5000,28.7500,[v1,staff1])-note(n134,75,13449,13547,61,1,1). +snote(n138,[E,n],5,15:1,3/16,1/16,28.7500,29.0000,[v1,staff1])-note(n135,76,13570,13709,58,1,1). +snote(n139,[C,n],6,15:2,0,1/16,29.0000,29.2500,[v1,staff1])-note(n136,84,13666,13790,67,1,1). +insertion-note(n137,81,13852,13931,23,1,1). +snote(n140,[G,n],5,15:2,1/16,1/16,29.2500,29.5000,[v1,staff1])-note(n138,79,13854,13892,31,1,1). +snote(n141,[F,n],5,15:2,1/8,1/16,29.5000,29.7500,[v1,staff1])-note(n139,77,13900,13997,55,1,1). +snote(n142,[E,n],5,15:2,3/16,1/16,29.7500,30.0000,[v1,staff1])-note(n141,76,14007,14217,62,1,1). +snote(n146,[C,n],4,15:2,3/16,1/16,29.7500,30.0000,[v5,staff2])-note(n140,60,13990,14130,52,1,1). +snote(n147,[G,n],5,16:1,0,3/16,30.0000,30.7500,[v1,staff1])-note(n144,79,14219,14738,68,1,1). +snote(n150,[E,n],4,16:1,0,3/16,30.0000,30.7500,[v5,staff2])-note(n143,64,14198,14709,44,1,1). +snote(n153,[G,n],3,16:1,0,1/2,30.0000,32.0000,[v6,staff2])-note(n142,55,14181,14897,45,1,1). +snote(n148,[E,n],5,16:1,3/16,1/16,30.7500,31.0000,[v1,staff1])-note(n146,76,14733,14901,47,1,1). +snote(n151,[C,n],4,16:1,3/16,1/16,30.7500,31.0000,[v5,staff2])-note(n145,60,14730,14872,43,1,1). +snote(n149,[D,n],5,16:2,0,1/4,31.0000,32.0000,[v1,staff1])-note(n148,74,14894,15140,44,1,1). +snote(n152,[B,n],3,16:2,0,1/4,31.0000,32.0000,[v5,staff2])-note(n147,59,14892,15103,51,1,1). +snote(n154,[D,n],5,17:1,0,1/16,32.0000,32.2500,[v1,staff1])-note(n150,74,15590,15672,58,1,1). +snote(n162,[C,n],3,17:1,0,1/4,32.0000,33.0000,[v5,staff2])-note(n149,48,15585,15661,61,1,1). +snote(n155,[C,n],5,17:1,1/16,1/16,32.2500,32.5000,[v1,staff1])-note(n151,72,15720,15756,39,1,1). +snote(n156,[B,n],4,17:1,1/8,1/16,32.5000,32.7500,[v1,staff1])-note(n152,71,15800,15856,54,1,1). +snote(n157,[C,n],5,17:1,3/16,1/16,32.7500,33.0000,[v1,staff1])-note(n153,72,15895,15993,55,1,1). +snote(n158,[B,n],4,17:2,0,1/16,33.0000,33.2500,[v1,staff1])-note(n155,71,16026,16096,48,1,1). +snote(n163,[C,n],4,17:2,0,1/4,33.0000,34.0000,[v5,staff2])-note(n154,60,16014,16068,59,1,1). +snote(n159,[C,n],5,17:2,1/16,1/16,33.2500,33.5000,[v1,staff1])-note(n156,72,16170,16214,43,1,1). +snote(n160,[B,n],4,17:2,1/8,1/16,33.5000,33.7500,[v1,staff1])-note(n157,71,16255,16315,59,1,1). +snote(n161,[C,n],5,17:2,3/16,1/16,33.7500,34.0000,[v1,staff1])-note(n158,72,16355,16377,49,1,1). +snote(n164,[A,n],5,18:1,0,1/16,34.0000,34.2500,[v1,staff1])-note(n160,81,16460,16530,25,1,1). +snote(n172,[E,n],4,18:1,0,1/4,34.0000,35.0000,[v5,staff2])-note(n159,64,16437,16500,59,1,1). +snote(n165,[G,n],5,18:1,1/16,1/16,34.2500,34.5000,[v1,staff1])-note(n161,79,16576,16647,63,1,1). +snote(n166,[F,#],5,18:1,1/8,1/16,34.5000,34.7500,[v1,staff1])-note(n162,78,16674,16734,58,1,1). +snote(n167,[G,n],5,18:1,3/16,1/16,34.7500,35.0000,[v1,staff1])-note(n163,79,16802,16826,42,1,1). +snote(n168,[F,#],5,18:2,0,1/16,35.0000,35.2500,[v1,staff1])-note(n164,78,16897,16950,54,1,1). +snote(n173,[C,n],4,18:2,0,1/4,35.0000,36.0000,[v5,staff2])-note(n165,60,16900,16961,47,1,1). +snote(n169,[G,n],5,18:2,1/16,1/16,35.2500,35.5000,[v1,staff1])-note(n166,79,17000,17030,44,1,1). +snote(n170,[F,#],5,18:2,1/8,1/16,35.5000,35.7500,[v1,staff1])-note(n167,78,17095,17149,55,1,1). +snote(n171,[G,n],5,18:2,3/16,1/16,35.7500,36.0000,[v1,staff1])-note(n168,79,17227,17239,32,1,1). +snote(n174,[G,#],5,19:1,0,1/16,36.0000,36.2500,[v1,staff1])-note(n169,80,17291,17379,60,1,1). +snote(n182,[F,n],4,19:1,0,1/4,36.0000,37.0000,[v5,staff2])-note(n170,65,17318,17379,50,1,1). +snote(n175,[A,n],5,19:1,1/16,1/16,36.2500,36.5000,[v1,staff1])-note(n171,81,17413,17516,64,1,1). +snote(n176,[C,n],6,19:1,1/8,1/16,36.5000,36.7500,[v1,staff1])-note(n172,84,17515,17625,60,1,1). +snote(n177,[B,n],5,19:1,3/16,1/16,36.7500,37.0000,[v1,staff1])-note(n173,83,17639,17715,44,1,1). +snote(n178,[D,n],6,19:2,0,1/16,37.0000,37.2500,[v1,staff1])-note(n174,86,17721,17846,61,1,1). +snote(n183,[C,n],4,19:2,0,1/4,37.0000,38.0000,[v5,staff2])-note(n175,60,17737,17822,46,1,1). +snote(n179,[C,n],6,19:2,1/16,1/16,37.2500,37.5000,[v1,staff1])-note(n176,84,17871,17948,42,1,1). +snote(n180,[B,n],5,19:2,1/8,1/16,37.5000,37.7500,[v1,staff1])-note(n177,83,17953,18033,55,1,1). +snote(n181,[A,n],5,19:2,3/16,1/16,37.7500,38.0000,[v1,staff1])-note(n178,81,18035,18067,58,1,1). +snote(n184,[A,n],5,20:1,0,1/16,38.0000,38.2500,[v1,staff1])-note(n181,81,18201,18252,55,1,1). +snote(n192,[C,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n179,60,18194,18582,48,1,1). +snote(n193,[E,n],4,20:1,0,1/4,38.0000,39.0000,[v5,staff2])-note(n180,64,18201,18543,55,1,1). +snote(n185,[G,n],5,20:1,1/16,1/16,38.2500,38.5000,[v1,staff1])-note(n182,79,18290,18385,67,1,1). +snote(n186,[E,n],6,20:1,1/8,1/16,38.5000,38.7500,[v1,staff1])-note(n183,88,18420,18561,66,1,1). +snote(n187,[D,n],6,20:1,3/16,1/16,38.7500,39.0000,[v1,staff1])-note(n184,86,18532,18660,63,1,1). +snote(n188,[C,n],6,20:2,0,1/16,39.0000,39.2500,[v1,staff1])-note(n185,84,18632,18742,71,1,1). +snote(n189,[B,n],5,20:2,1/16,1/16,39.2500,39.5000,[v1,staff1])-note(n186,83,18721,18794,68,1,1). +snote(n190,[A,n],5,20:2,1/8,1/16,39.5000,39.7500,[v1,staff1])-note(n187,81,18813,18856,60,1,1). +snote(n191,[G,n],5,20:2,3/16,1/16,39.7500,40.0000,[v1,staff1])-note(n189,79,18948,18977,71,1,1). +snote(n195,[C,#],4,20:2,3/16,1/16,39.7500,40.0000,[v5,staff2])-note(n188,61,18940,18986,58,1,1). +snote(n196,[G,n],5,21:1,0,1/16,40.0000,40.2500,[v1,staff1])-note(n190,79,19065,19157,77,1,1). +snote(n204,[D,n],4,21:1,0,1/4,40.0000,41.0000,[v5,staff2])-note(n191,62,19087,19522,74,1,1). +snote(n197,[F,n],5,21:1,1/16,1/16,40.2500,40.5000,[v1,staff1])-note(n192,77,19185,19289,84,1,1). +snote(n198,[D,n],6,21:1,1/8,1/16,40.5000,40.7500,[v1,staff1])-note(n193,86,19293,19411,75,1,1). +snote(n199,[C,n],6,21:1,3/16,1/16,40.7500,41.0000,[v1,staff1])-note(n194,84,19409,19543,62,1,1). +snote(n200,[B,n],5,21:2,0,1/16,41.0000,41.2500,[v1,staff1])-note(n195,83,19506,19618,71,1,1). +snote(n201,[A,n],5,21:2,1/16,1/16,41.2500,41.5000,[v1,staff1])-note(n196,81,19616,19671,45,1,1). +snote(n202,[G,n],5,21:2,1/8,1/16,41.5000,41.7500,[v1,staff1])-note(n197,79,19679,19734,70,1,1). +snote(n203,[F,n],5,21:2,3/16,1/16,41.7500,42.0000,[v1,staff1])-note(n198,77,19810,19845,81,1,1). +snote(n206,[B,n],3,21:2,3/16,1/16,41.7500,42.0000,[v5,staff2])-note(n199,59,19840,19876,47,1,1). +snote(n207,[F,n],5,22:1,0,1/16,42.0000,42.2500,[v1,staff1])-note(n200,77,19948,20032,77,1,1). +snote(n215,[C,n],4,22:1,0,1/4,42.0000,43.0000,[v5,staff2])-note(n201,60,19981,20353,69,1,1). +snote(n208,[E,n],5,22:1,1/16,1/16,42.2500,42.5000,[v1,staff1])-note(n202,76,20053,20156,77,1,1). +snote(n209,[C,n],6,22:1,1/8,1/16,42.5000,42.7500,[v1,staff1])-note(n203,84,20176,20270,68,1,1). +snote(n210,[B,n],5,22:1,3/16,1/16,42.7500,43.0000,[v1,staff1])-note(n204,83,20276,20417,62,1,1). +snote(n211,[A,n],5,22:2,0,1/16,43.0000,43.2500,[v1,staff1])-note(n205,81,20379,20485,59,1,1). +snote(n212,[G,n],5,22:2,1/16,1/16,43.2500,43.5000,[v1,staff1])-note(n206,79,20462,20534,71,1,1). +snote(n213,[F,n],5,22:2,1/8,1/16,43.5000,43.7500,[v1,staff1])-note(n207,77,20557,20635,72,1,1). +snote(n214,[E,n],5,22:2,3/16,1/16,43.7500,44.0000,[v1,staff1])-note(n208,76,20663,20743,76,1,1). +snote(n217,[A,n],3,22:2,3/16,1/16,43.7500,44.0000,[v5,staff2])-note(n209,57,20675,20787,61,1,1). +snote(n218,[D,n],5,23:1,0,1/8,44.0000,44.5000,[v1,staff1])-note(n210,74,20796,21067,64,1,1). +snote(n222,[F,n],3,23:1,0,1/4,44.0000,45.0000,[v5,staff2])-note(n211,53,20854,21113,72,1,1). +snote(n219,[A,n],5,23:1,1/8,1/8,44.5000,45.0000,[v1,staff1])-note(n212,81,21064,21476,68,1,1). +snote(n220,[G,n],5,23:2,0,1/8,45.0000,45.5000,[v1,staff1])-note(n214,79,21296,21543,75,1,1). +snote(n223,[G,n],3,23:2,0,1/4,45.0000,46.0000,[v5,staff2])-note(n213,55,21287,21555,70,1,1). +snote(n221,[B,n],4,23:2,1/8,1/8,45.5000,46.0000,[v1,staff1])-note(n215,71,21540,21748,58,1,1). +snote(n224,[C,n],5,24:1,0,1/4,46.0000,47.0000,[v1,staff1])-note(n216,72,21793,22915,48,1,1). +snote(n226,[C,n],4,24:1,0,1/4,46.0000,47.0000,[v5,staff2])-note(n217,60,21819,22029,49,1,1). +snote(n227,[C,n],3,24:2,0,1/4,47.0000,48.0000,[v5,staff2])-note(n218,48,22393,22966,50,1,1). sustain(0,0). sustain(3316,67). sustain(3346,127). diff --git a/tests/test_tonal_tension.py b/tests/test_tonal_tension.py index e80d8b34..b7a6f970 100644 --- a/tests/test_tonal_tension.py +++ b/tests/test_tonal_tension.py @@ -26,9 +26,9 @@ class TestTonalTension(unittest.TestCase): def test_prepare_notearray(self): target_note_array = np.array( [ - (0.0, 2.0, 69, 64, "n1", "A", 0, 4, 0, -1), - (1.0, 1.0, 72, 64, "n2", "C", 0, 5, 0, -1), - (1.0, 1.0, 76, 64, "n3", "E", 0, 5, 0, -1), + (0.0, 2.0, 69, 64, "n0", "A", 0, 4, 0, -1), + (1.0, 1.0, 72, 64, "n1", "C", 0, 5, 0, -1), + (1.0, 1.0, 76, 64, "n2", "E", 0, 5, 0, -1), ], dtype=[ ("onset_sec", " Date: Mon, 25 Sep 2023 12:57:42 +0200 Subject: [PATCH 71/80] update CHANGES.md --- CHANGES.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 8fd051cb..7ce264e5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,7 +26,6 @@ Bug Fixes Other Changes ------------ * new unit test for cross-staff beaming for musicxml -* note ID numbering starts at 1 (instead of 0) when creating a performed part from MIDI Version 1.3.1 (Released on 2023-07-06) From c7e24cedf1e12a7b00c1d7abb6c98c3fa5513b1a Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 25 Sep 2023 14:13:35 +0200 Subject: [PATCH 72/80] Changed PerformedNote object to not directly inherit from a dict but to perform like one. This avoids internal recursions. --- partitura/performance.py | 99 +++++++++++++++++++++++----------------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index eb518737..d2339ea1 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -284,7 +284,7 @@ def adjust_offsets_w_sustain( note["sound_off"] = offset -class PerformedNote(dict): +class PerformedNote: """ A dictionary-like object representing a performed note. @@ -299,15 +299,15 @@ class PerformedNote(dict): """ def __init__(self, pnote_dict): - super().__init__(pnote_dict) - self["id"] = self.get("id", None) - self["pitch"] = self.get("pitch", self["midi_pitch"]) - self["note_on"] = self.get("note_on", -1) - self["note_off"] = self.get("note_off", -1) - self["sound_off"] = self.get("sound_off", self["note_off"]) - self["track"] = self.get("track", 0) - self["channel"] = self.get("channel", 1) - self["velocity"] = self.get("velocity", 60) + self.pnote_dict = pnote_dict + self.pnote_dict["id"] = self.pnote_dict.get("id", None) + self.pnote_dict["pitch"] = self.pnote_dict.get("pitch", self["midi_pitch"]) + self.pnote_dict["note_on"] = self.pnote_dict.get("note_on", -1) + self.pnote_dict["note_off"] = self.pnote_dict.get("note_off", -1) + self.pnote_dict["sound_off"] = self.pnote_dict.get("sound_off", self["note_off"]) + self.pnote_dict["track"] = self.pnote_dict.get("track", 0) + self.pnote_dict["channel"] = self.pnote_dict.get("channel", 1) + self.pnote_dict["velocity"] = self.pnote_dict.get("velocity", 60) self._validate_values() self._accepted_keys = [ "id", @@ -319,7 +319,6 @@ def __init__(self, pnote_dict): "channel", "sound_off", ] - self.__setitem__ = self._setitem_new def __str__(self): return f"PerformedNote: {self['id']}" @@ -333,6 +332,12 @@ def __eq__(self, other): np.array([self[k] == other[k] for k in self.keys() if k in other.keys()]) ) + def keys(self): + return self.pnote_dict.keys() + + def get(self, key, default=None): + return self.pnote_dict.get(key, default) + def __hash__(self): return hash(self["id"]) @@ -349,33 +354,13 @@ def __ge__(self, other): return self["note_on"] >= other["note_on"] def __getitem__(self, key): - return self.get(key, None) + return self.pnote_dict.get(key, None) - def _setitem_new(self, key, value): + def __setitem__(self, key, value): if key not in self._accepted_keys: - raise KeyError(f"Key {key} not in PerformedNote") - elif key == "note_off": - # Verify that the note_off is after the note_on - if value < self["note_on"]: - raise ValueError(f"note_off must be after or equal to note_on") - self["sound_off"] = ( - value if self["sound_off"] < value else self["sound_off"] - ) - self["note_off"] = value - elif key == "note_on": - # Verify that the note_on is before the note_off - if value > self["note_off"]: - raise ValueError(f"note_on must be before or equal to note_off") - - self["duration_sec"] = self["note_off"] - value - self["note_on"] = value - elif key == "sound_off": - # Verify that the sound_off is after the note_off - if value <= self["note_off"]: - raise ValueError(f"sound_off must be after or equal to note_off") - self["sound_off"] = value - else: - self[key] = value + raise KeyError(f"Key {key} not accepted for PerformedNote") + self.pnote_dict[key] = value + self._validate_values(key) def __delitem__(self, key): raise KeyError("Cannot delete items from PerformedNote") @@ -389,19 +374,49 @@ def __len__(self): def __contains__(self, key): return key in self.keys() - def _validate_values(self): - if self["pitch"] > 127 or self["pitch"] < 0: - raise ValueError(f"pitch must be between 0 and 127") - if self["note_on"] < 0: + def _validate_values(self, key=None): + if key is None: + keys = self.keys() + else: + keys = [key] + + for key in keys: + if key == "pitch": + self._validate_pitch() + elif key == "note_on": + self._validate_note_on() + elif key == "note_off": + self._validate_note_off() + elif key == "velocity": + self._validate_velocity() + elif key == "sound_off": + self._validate_sound_off() + + def _validate_sound_off(self): + if self.pnote_dict["sound_off"] < self.pnote_dict["note_off"]: + raise ValueError(f"sound_off must be greater or equal to note_off") + + def _validate_note_on(self): + if self.pnote_dict["note_on"] < 0: raise ValueError( f"Note on value provided is invalid, must be greater than or equal to 0" ) - if self["note_off"] < 0 or self["note_off"] < self["note_on"]: + + def _validate_note_off(self): + if self.pnote_dict.get("note_on", -1) < 0: + return + if self.pnote_dict["note_off"] < 0 or self.pnote_dict["note_off"] < self.pnote_dict["note_on"]: raise ValueError( f"Note off value provided is invalid, " f"must be greater than or equal to 0 and greater or equal to note_on" ) - if self["velocity"] > 127 or self["velocity"] < 0: + + def _validate_pitch(self): + if self.pnote_dict["pitch"] > 127 or self.pnote_dict["pitch"] < 0: + raise ValueError(f"pitch must be between 0 and 127") + + def _validate_velocity(self): + if self.pnote_dict["velocity"] > 127 or self.pnote_dict["velocity"] < 0: raise ValueError(f"velocity must be between 0 and 127") From b6bed0ed853c5bf1dcd6547d7ed0e00e78a2fc33 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 25 Sep 2023 14:14:55 +0200 Subject: [PATCH 73/80] Changed PerformedNote object to not directly inherit from a dict but to perform like one. This avoids internal recursions. --- partitura/performance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/partitura/performance.py b/partitura/performance.py index d2339ea1..fef3bbe1 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -393,6 +393,8 @@ def _validate_values(self, key=None): self._validate_sound_off() def _validate_sound_off(self): + if self.get("note_off", -1) < 0: + return if self.pnote_dict["sound_off"] < self.pnote_dict["note_off"]: raise ValueError(f"sound_off must be greater or equal to note_off") From 7b68675c90182baec65775c46337def03c7c8596 Mon Sep 17 00:00:00 2001 From: manoskary Date: Mon, 25 Sep 2023 12:15:32 +0000 Subject: [PATCH 74/80] Format code with black (bot) --- partitura/performance.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index fef3bbe1..edfb64e5 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -304,7 +304,9 @@ def __init__(self, pnote_dict): self.pnote_dict["pitch"] = self.pnote_dict.get("pitch", self["midi_pitch"]) self.pnote_dict["note_on"] = self.pnote_dict.get("note_on", -1) self.pnote_dict["note_off"] = self.pnote_dict.get("note_off", -1) - self.pnote_dict["sound_off"] = self.pnote_dict.get("sound_off", self["note_off"]) + self.pnote_dict["sound_off"] = self.pnote_dict.get( + "sound_off", self["note_off"] + ) self.pnote_dict["track"] = self.pnote_dict.get("track", 0) self.pnote_dict["channel"] = self.pnote_dict.get("channel", 1) self.pnote_dict["velocity"] = self.pnote_dict.get("velocity", 60) @@ -407,7 +409,10 @@ def _validate_note_on(self): def _validate_note_off(self): if self.pnote_dict.get("note_on", -1) < 0: return - if self.pnote_dict["note_off"] < 0 or self.pnote_dict["note_off"] < self.pnote_dict["note_on"]: + if ( + self.pnote_dict["note_off"] < 0 + or self.pnote_dict["note_off"] < self.pnote_dict["note_on"] + ): raise ValueError( f"Note off value provided is invalid, " f"must be greater than or equal to 0 and greater or equal to note_on" From b838081ad1762fd5425d969bec4bbaf4a12e5c8d Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 25 Sep 2023 14:20:05 +0200 Subject: [PATCH 75/80] Added a copy method to PerformedNote. --- partitura/performance.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/partitura/performance.py b/partitura/performance.py index fef3bbe1..447cd4ca 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -413,6 +413,9 @@ def _validate_note_off(self): f"must be greater than or equal to 0 and greater or equal to note_on" ) + def copy(self): + return PerformedNote(self.pnote_dict.copy()) + def _validate_pitch(self): if self.pnote_dict["pitch"] > 127 or self.pnote_dict["pitch"] < 0: raise ValueError(f"pitch must be between 0 and 127") From e279f2d0344ca44ccd16387a69f1375ee540d4da Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 25 Sep 2023 14:25:48 +0200 Subject: [PATCH 76/80] Added note_on_tick and note_off_tick to accepted keys. --- partitura/performance.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index b4dd9e11..e08d0ca3 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -320,6 +320,8 @@ def __init__(self, pnote_dict): "track", "channel", "sound_off", + "note_on_tick", + "note_off_tick", ] def __str__(self): @@ -376,6 +378,9 @@ def __len__(self): def __contains__(self, key): return key in self.keys() + def copy(self): + return PerformedNote(self.pnote_dict.copy()) + def _validate_values(self, key=None): if key is None: keys = self.keys() @@ -418,8 +423,23 @@ def _validate_note_off(self): f"must be greater than or equal to 0 and greater or equal to note_on" ) - def copy(self): - return PerformedNote(self.pnote_dict.copy()) + def _validate_note_on_tick(self): + if self.pnote_dict["note_on_tick"] < 0: + raise ValueError( + f"Note on tick value provided is invalid, must be greater than or equal to 0" + ) + + def _validate_note_off_tick(self): + if self.pnote_dict.get("note_on_tick", -1) < 0: + return + if ( + self.pnote_dict["note_off_tick"] < 0 + or self.pnote_dict["note_off_tick"] < self.pnote_dict["note_on_tick"] + ): + raise ValueError( + f"Note off tick value provided is invalid, " + f"must be greater than or equal to 0 and greater or equal to note_on_tick" + ) def _validate_pitch(self): if self.pnote_dict["pitch"] > 127 or self.pnote_dict["pitch"] < 0: From 702adf70ab6c20695fe190ccf1777b846f87e088 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 25 Sep 2023 17:06:05 +0200 Subject: [PATCH 77/80] Corrections on validation of PerformedNotes. --- partitura/performance.py | 67 +++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index e08d0ca3..543176fc 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -85,7 +85,7 @@ def __init__( super().__init__() self.id = id self.part_name = part_name - self.notes = list(map(lambda n: PerformedNote(n), notes)) + self.notes = list(map(lambda n: n if isinstance(n, PerformedNote) else PerformedNote(n), notes)) self.controls = controls or [] self.programs = programs or [] self.ppq = ppq @@ -310,7 +310,7 @@ def __init__(self, pnote_dict): self.pnote_dict["track"] = self.pnote_dict.get("track", 0) self.pnote_dict["channel"] = self.pnote_dict.get("channel", 1) self.pnote_dict["velocity"] = self.pnote_dict.get("velocity", 60) - self._validate_values() + self._validate_values(pnote_dict) self._accepted_keys = [ "id", "pitch", @@ -363,8 +363,8 @@ def __getitem__(self, key): def __setitem__(self, key, value): if key not in self._accepted_keys: raise KeyError(f"Key {key} not accepted for PerformedNote") + self._validate_values((key, value)) self.pnote_dict[key] = value - self._validate_values(key) def __delitem__(self, key): raise KeyError("Cannot delete items from PerformedNote") @@ -381,72 +381,77 @@ def __contains__(self, key): def copy(self): return PerformedNote(self.pnote_dict.copy()) - def _validate_values(self, key=None): - if key is None: - keys = self.keys() + def _validate_values(self, d): + if isinstance(d, dict): + dd = d + elif isinstance(d, tuple): + dd = {d[0]: d[1]} else: - keys = [key] + raise ValueError(f"Invalid value {d} provided for PerformedNote") - for key in keys: + for key, value in dd.items(): if key == "pitch": - self._validate_pitch() + self._validate_pitch(value) elif key == "note_on": - self._validate_note_on() + self._validate_note_on(value) elif key == "note_off": - self._validate_note_off() + self._validate_note_off(value) elif key == "velocity": - self._validate_velocity() + self._validate_velocity(value) elif key == "sound_off": - self._validate_sound_off() + self._validate_sound_off(value) - def _validate_sound_off(self): + + def _validate_sound_off(self, value): if self.get("note_off", -1) < 0: return - if self.pnote_dict["sound_off"] < self.pnote_dict["note_off"]: + if value < 0: + raise ValueError(f"sound_off must be greater than or equal to 0") + if value < self.pnote_dict["note_off"]: raise ValueError(f"sound_off must be greater or equal to note_off") - def _validate_note_on(self): - if self.pnote_dict["note_on"] < 0: + def _validate_note_on(self, value): + if value < 0: raise ValueError( f"Note on value provided is invalid, must be greater than or equal to 0" ) - def _validate_note_off(self): + def _validate_note_off(self, value): if self.pnote_dict.get("note_on", -1) < 0: return if ( - self.pnote_dict["note_off"] < 0 - or self.pnote_dict["note_off"] < self.pnote_dict["note_on"] + value < 0 + or value < self.pnote_dict["note_on"] ): raise ValueError( f"Note off value provided is invalid, " - f"must be greater than or equal to 0 and greater or equal to note_on" + f"must be a positive value greater than or equal to 0 and greater or equal to note_on" ) - def _validate_note_on_tick(self): - if self.pnote_dict["note_on_tick"] < 0: + def _validate_note_on_tick(self, value): + if value < 0: raise ValueError( f"Note on tick value provided is invalid, must be greater than or equal to 0" ) - def _validate_note_off_tick(self): + def _validate_note_off_tick(self, value): if self.pnote_dict.get("note_on_tick", -1) < 0: return if ( - self.pnote_dict["note_off_tick"] < 0 - or self.pnote_dict["note_off_tick"] < self.pnote_dict["note_on_tick"] + value < 0 + or value < self.pnote_dict["note_on_tick"] ): raise ValueError( f"Note off tick value provided is invalid, " - f"must be greater than or equal to 0 and greater or equal to note_on_tick" + f"must be a positive value greater than or equal to 0 and greater or equal to note_on_tick" ) - def _validate_pitch(self): - if self.pnote_dict["pitch"] > 127 or self.pnote_dict["pitch"] < 0: + def _validate_pitch(self, value): + if value > 127 or value < 0: raise ValueError(f"pitch must be between 0 and 127") - def _validate_velocity(self): - if self.pnote_dict["velocity"] > 127 or self.pnote_dict["velocity"] < 0: + def _validate_velocity(self, value): + if value > 127 or value < 0: raise ValueError(f"velocity must be between 0 and 127") From 2e58c9bf0d30ba52987e48adb87fc33c8a4ec9c9 Mon Sep 17 00:00:00 2001 From: manoskary Date: Mon, 25 Sep 2023 15:06:29 +0000 Subject: [PATCH 78/80] Format code with black (bot) --- partitura/performance.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/partitura/performance.py b/partitura/performance.py index 543176fc..02dbe29d 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -85,7 +85,11 @@ def __init__( super().__init__() self.id = id self.part_name = part_name - self.notes = list(map(lambda n: n if isinstance(n, PerformedNote) else PerformedNote(n), notes)) + self.notes = list( + map( + lambda n: n if isinstance(n, PerformedNote) else PerformedNote(n), notes + ) + ) self.controls = controls or [] self.programs = programs or [] self.ppq = ppq @@ -401,7 +405,6 @@ def _validate_values(self, d): elif key == "sound_off": self._validate_sound_off(value) - def _validate_sound_off(self, value): if self.get("note_off", -1) < 0: return @@ -419,10 +422,7 @@ def _validate_note_on(self, value): def _validate_note_off(self, value): if self.pnote_dict.get("note_on", -1) < 0: return - if ( - value < 0 - or value < self.pnote_dict["note_on"] - ): + if value < 0 or value < self.pnote_dict["note_on"]: raise ValueError( f"Note off value provided is invalid, " f"must be a positive value greater than or equal to 0 and greater or equal to note_on" @@ -437,10 +437,7 @@ def _validate_note_on_tick(self, value): def _validate_note_off_tick(self, value): if self.pnote_dict.get("note_on_tick", -1) < 0: return - if ( - value < 0 - or value < self.pnote_dict["note_on_tick"] - ): + if value < 0 or value < self.pnote_dict["note_on_tick"]: raise ValueError( f"Note off tick value provided is invalid, " f"must be a positive value greater than or equal to 0 and greater or equal to note_on_tick" From 6151ee949fc25d87fc3d1c553fb27660dcb3571a Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 25 Sep 2023 17:08:10 +0200 Subject: [PATCH 79/80] Added tick validation in the loop. --- partitura/performance.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/partitura/performance.py b/partitura/performance.py index 543176fc..4348e32b 100644 --- a/partitura/performance.py +++ b/partitura/performance.py @@ -400,7 +400,10 @@ def _validate_values(self, d): self._validate_velocity(value) elif key == "sound_off": self._validate_sound_off(value) - + elif key == "note_on_tick": + self._validate_note_on_tick(value) + elif key == "note_off_tick": + self._validate_note_off_tick(value) def _validate_sound_off(self, value): if self.get("note_off", -1) < 0: From fe4dbeacaf67a491b9f809bc6720a7e7e7b6ccf7 Mon Sep 17 00:00:00 2001 From: melkisedeath Date: Mon, 25 Sep 2023 17:53:39 +0200 Subject: [PATCH 80/80] Workaround to check for screen only on linux machines to avoid missing screens on servers. --- partitura/io/musescore.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/partitura/io/musescore.py b/partitura/io/musescore.py index 7b976993..1ab934db 100644 --- a/partitura/io/musescore.py +++ b/partitura/io/musescore.py @@ -91,7 +91,8 @@ def find_musescore(): ) else: raise MuseScoreNotFoundException() - if "DISPLAY" not in os.environ: + # check if a screen is available (only on Linux) + if "DISPLAY" not in os.environ and platform.system() == "Linux": raise MuseScoreNotFoundException( "Musescore Executable was found, but a screen is missing. Musescore needs a screen to load scores" )