Skip to content

Commit

Permalink
Merge pull request #335 from CPJKU/develop
Browse files Browse the repository at this point in the history
PR for Release 1.4.1
  • Loading branch information
fosfrancesco authored Oct 25, 2023
2 parents ef67b95 + 8ee8ba9 commit 8812ca6
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 16 deletions.
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Release Notes
=============

Version 1.4.1 (Released on 2023-10-25)
--------------------------------------

## Bug Fixes
- remove unnecessary escape characters for correct parsing of sharp accidentals in Nakamura match files.
- don't consider the propriety `doc_order` for sorting notes in the `matchfile_from_alignment` function if it is not present. This propriety is only present in parts from musicxml scores and previously resulted in an exception for other score formats. This solves https://github.com/CPJKU/partitura/issues/326
- during matchfile parsing, voice info is now parsed as follows: If there is no voice info, all notes get assigned voice number 1. If there is only voice info for the solo voice, the non-solo voiced notes get voice 2. If multiple notes have different voices, but not every note has a voice annotated, those with voice annotation get the annotated voice number and those without voice annotation get assigned the max voice+1 voice number. Previously all notes were assigned to voice 1 if there were any None voiced note
- during matchfile parsing, all note classes are now matched correctly. Previously classes `MatchSnoteTrailingScore` and `MatchSnoteNoPlayedNote` were always marked as `MatchSnoteDeletion` and `MatchHammerBounceNote`, `MatchTrailingPlayedNote`, `MatchTrillNote` always ended up as `MatchInsertionNote`. This solves https://github.com/CPJKU/partitura/issues/286
- during matchfile parsing, lines which can't be parsed are removed. Before they ended up as `None` in the output.

Version 1.4.0 (Released on 2023-09-22)
--------------------------------------

Expand Down
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@
# built documents.
#
# The short X.Y version.
version = "1.4.0" # pkg_resources.get_distribution("partitura").version
version = "1.4.1" # pkg_resources.get_distribution("partitura").version
# The full version, including alpha/beta/rc tags.
release = "1.4.0"
release = "1.4.1"

# # The full version, including alpha/beta/rc tags
# release = pkg_resources.get_distribution("partitura").version
Expand Down
5 changes: 4 additions & 1 deletion partitura/io/exportmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,10 @@ def matchfile_from_alignment(
offset_in_beats=offset_beats,
score_attributes_list=score_attributes_list,
)
snote_sort_info[snote.id] = (onset_beats, snote.doc_order)
snote_sort_info[snote.id] = (
onset_beats,
snote.doc_order if snote.doc_order is not None else 0,
)

# # 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:
Expand Down
26 changes: 20 additions & 6 deletions partitura/io/importmatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from partitura.io.matchfile_utils import (
Version,
number_pattern,
vnumber_pattern,
MatchTimeSignature,
MatchKeySignature,
format_pnote_id,
Expand Down Expand Up @@ -209,7 +210,9 @@ def load_matchfile(
parse_matchline, version=version, from_matchline_methods=from_matchline_methods
)
f_vec = np.vectorize(f)
parsed_lines = f_vec(np_lines).tolist()
parsed_lines_raw = f_vec(np_lines)
# do not return unparseable lines
parsed_lines = parsed_lines_raw[parsed_lines_raw != None].tolist()
# Create MatchFile instance
mf = MatchFile(lines=parsed_lines)
# Validate match for duplicate snote_ids or pnote_ids
Expand Down Expand Up @@ -632,6 +635,15 @@ def part_from_matchfile(

if "s" in note.ScoreAttributesList:
note_attributes["voice"] = 1
elif any(a.startswith("v") for a in note.ScoreAttributesList):
note_attributes["voice"] = next(
(
int(a[1:])
for a in note.ScoreAttributesList
if vnumber_pattern.match(a)
),
None,
)
else:
note_attributes["voice"] = next(
(int(a) for a in note.ScoreAttributesList if number_pattern.match(a)),
Expand Down Expand Up @@ -692,9 +704,7 @@ def part_from_matchfile(

else:
part_note = score.Note(**note_attributes)

part.add(part_note, onset_divs, offset_divs)

# Check if the note is tied and if so, add the tie information
if is_tied:
found = False
Expand All @@ -717,7 +727,6 @@ def part_from_matchfile(
part_note.id
)
)

# add time signatures
for ts_beat_time, ts_bar, tsg in ts:
ts_beats = tsg.numerator
Expand All @@ -729,7 +738,6 @@ def part_from_matchfile(
else:
bar_start_divs = 0
part.add(score.TimeSignature(ts_beats, ts_beat_type), bar_start_divs)

# add key signatures
for ks_beat_time, ks_bar, keys in mf.key_signatures:
if ks_bar in bar_times.keys():
Expand All @@ -755,10 +763,16 @@ def part_from_matchfile(
score.tie_notes(part)
score.find_tuplets(part)

if not all([n.voice for n in part.notes_tied]):
n_voices = set([n.voice for n in part.notes])
if len(n_voices) == 1 and None in n_voices:
for note in part.notes_tied:
if note.voice is None:
note.voice = 1
elif len(n_voices) > 1 and None in n_voices:
n_voices.remove(None)
for note in part.notes_tied:
if note.voice is None:
note.voice = max(n_voices) + 1

return part

Expand Down
4 changes: 1 addition & 3 deletions partitura/io/importnakamura.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,7 @@ def load_nakamuramatch(filename: PathLike) -> Tuple[Union[np.ndarray, list]]:
# load missing notes
missing = np.fromregex(filename, pattern, dtype=dtype_missing)

midi_pitch = np.array(
[note_name_to_midi_pitch(n.replace("#", r"\#")) for n in result["alignSitch"]]
)
midi_pitch = np.array([note_name_to_midi_pitch(n) for n in result["alignSitch"]])

align_valid = result["alignID"] != "*"
n_align = sum(align_valid)
Expand Down
15 changes: 14 additions & 1 deletion partitura/io/matchfile_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ def prepare_kwargs_from_matchline(

class BaseDeletionLine(MatchLine):
out_pattern = "{SnoteLine}-deletion."
identifier_pattern = re.compile(r"-deletion\.")

def __init__(self, version: Version, snote: BaseSnoteLine) -> None:
super().__init__(version)
Expand Down Expand Up @@ -799,7 +800,12 @@ def prepare_kwargs_from_matchline(
matchline: str,
snote_class: BaseSnoteLine,
version: Version,
pos: int = 0,
) -> Dict:
match_pattern = cls.identifier_pattern.search(matchline, pos=pos)

if match_pattern is None:
raise MatchError("Input match line does not fit the expected pattern.")
snote = snote_class.from_matchline(matchline, version=version)

kwargs = dict(
Expand All @@ -812,6 +818,7 @@ def prepare_kwargs_from_matchline(

class BaseInsertionLine(MatchLine):
out_pattern = "insertion-{NoteLine}"
identifier_pattern = re.compile(r"insertion-")

def __init__(self, version: Version, note: BaseNoteLine) -> None:
super().__init__(version)
Expand Down Expand Up @@ -841,7 +848,13 @@ def prepare_kwargs_from_matchline(
matchline: str,
note_class: BaseNoteLine,
version: Version,
pos: int = 0,
) -> Dict:
match_pattern = cls.identifier_pattern.search(matchline, pos=pos)

if match_pattern is None:
raise MatchError("Input match line does not fit the expected pattern.")

note = note_class.from_matchline(matchline, version=version)

kwargs = dict(
Expand Down Expand Up @@ -896,7 +909,7 @@ def prepare_kwargs_from_matchline(
anchor_pattern = cls.ornament_pattern.search(matchline)

if anchor_pattern is None:
raise MatchError("")
raise MatchError("Input match line does not fit the expected pattern.")

anchor = interpret_as_string(anchor_pattern.group("Anchor"))
note = note_class.from_matchline(matchline, version=version)
Expand Down
1 change: 1 addition & 0 deletions partitura/io/matchfile_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
pitch_class_pattern = re.compile("(?P<step>[A-Ga-g])(?P<alter>[#bn]*)")

number_pattern = re.compile(r"\d+")
vnumber_pattern = re.compile(r"v\d+")

# For matchfiles before 1.0.0.
old_version_pattern = re.compile(r"^(?P<minor>[0-9]+)\.(?P<patch>[0-9]+)")
Expand Down
6 changes: 5 additions & 1 deletion partitura/io/matchlines_v0.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,7 @@ def from_matchline(

class MatchSnoteTrailingScore(MatchSnoteDeletion):
out_pattern = "{SnoteLine}-trailing_score_note."
identifier_pattern = re.compile(r"-trailing_score_note\.")

def __init__(self, version: Version, snote: MatchSnote) -> None:
super().__init__(version=version, snote=snote)
Expand All @@ -815,6 +816,7 @@ def __init__(self, version: Version, snote: MatchSnote) -> None:

class MatchSnoteNoPlayedNote(MatchSnoteDeletion):
out_pattern = "{SnoteLine}-no_played_note."
identifier_pattern = re.compile(r"-no_played_note\.")

def __init__(self, version: Version, snote: MatchSnote) -> None:
super().__init__(version=version, snote=snote)
Expand Down Expand Up @@ -848,6 +850,7 @@ def from_matchline(

class MatchHammerBounceNote(MatchInsertionNote):
out_pattern = "hammer_bounce-{NoteLine}"
identifier_pattern = re.compile(r"hammer_bounce-")

def __init__(self, version: Version, note: MatchNote) -> None:
super().__init__(version=version, note=note)
Expand All @@ -856,6 +859,7 @@ def __init__(self, version: Version, note: MatchNote) -> None:

class MatchTrailingPlayedNote(MatchInsertionNote):
out_pattern = "trailing_played_note-{NoteLine}"
identifier_pattern = re.compile(r"trailing_played_note-")

def __init__(self, version: Version, note: MatchNote) -> None:
super().__init__(version=version, note=note)
Expand Down Expand Up @@ -890,7 +894,7 @@ def from_matchline(
anchor_pattern = cls.ornament_pattern.search(matchline)

if anchor_pattern is None:
raise MatchError("")
raise MatchError("Input match line does not fit the expected pattern.")
note = MatchNote.from_matchline(matchline, version=version)

return cls(
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
EMAIL = "[email protected]"
AUTHOR = "Maarten Grachten, Carlos Cancino-Chacón, Silvan Peter, Emmanouil Karystinaios, Francesco Foscarin, Thassilo Gadermaier, Patricia Hu"
REQUIRES_PYTHON = ">=3.7"
VERSION = "1.4.0"
VERSION = "1.4.1"

# What packages are required for this module to be executed?
REQUIRED = ["numpy", "scipy", "lxml", "lark-parser", "xmlschema", "mido"]
Expand Down
1 change: 1 addition & 0 deletions tests/data/match/mozart_k265_var1.match
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ snote(n10,[C,n],4,1:2,0,1/4,1.0000,2.0000,[v5,staff2])-note(n5,60,1122,1160,67,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).
badly_formatted_line-note(n11,81,1556,1637,55,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).
Expand Down
6 changes: 5 additions & 1 deletion tests/test_match_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def test_load_match(self):
)

sna_musicxml = score_musicxml.note_array()
assert np.all(sna_match['voice'] == sna_musicxml['voice'])

for note in alignment:

Expand Down Expand Up @@ -564,7 +565,7 @@ def test_stimeptime_lines(self):
self.assertTrue(True)

def test_snote_lines(self):

snote_lines = [
"snote(n1,[B,n],3,0:2,1/8,1/8,-0.5000,0.0000,[v1])",
"snote(n3,[G,#],3,1:1,0,1/16,0.0000,0.2500,[v3])",
Expand Down Expand Up @@ -1992,3 +1993,6 @@ def test_match_key_signature(self):
for component in ks.other_components:
key_name = fifths_mode_to_key_name(component.fifths, component.mode)
self.assertTrue(str(component).startswith(key_name))

if __name__ == "__main__":
unittest.main()

0 comments on commit 8812ca6

Please sign in to comment.