3737 FractionalSymbolicDuration ,
3838 MatchKeySignature ,
3939 MatchTimeSignature ,
40+ MatchTempoIndication ,
4041 Version ,
4142)
4243
@@ -71,6 +72,8 @@ def matchfile_from_alignment(
7172 score_filename : Optional [PathLike ] = None ,
7273 performance_filename : Optional [PathLike ] = None ,
7374 assume_part_unfolded : bool = False ,
75+ tempo_indication : Optional [str ] = None ,
76+ diff_score_version_notes : Optional [list ] = None ,
7477 version : Version = LATEST_VERSION ,
7578 debug : bool = False ,
7679) -> MatchFile :
@@ -106,6 +109,10 @@ def matchfile_from_alignment(
106109 repetitions in the alignment. If False, the part will be automatically
107110 unfolded to have maximal coverage of the notes in the alignment.
108111 See `partitura.score.unfold_part_alignment`.
112+ tempo_indication : str or None
113+ The tempo direction indicated in the beginning of the score
114+ diff_score_version_notes : list or None
115+ A list of score notes that reflect a special score version (e.g., original edition/Erstdruck, Editors note etc.)
109116 version: Version
110117 Version of the match file. For now only 1.0.0 is supported.
111118 Returns
@@ -199,7 +206,6 @@ def matchfile_from_alignment(
199206
200207 # Score prop header lines
201208 scoreprop_lines = defaultdict (list )
202-
203209 # For score notes
204210 score_info = dict ()
205211 # Info for sorting lines
@@ -276,7 +282,6 @@ def matchfile_from_alignment(
276282 # Get all notes in the measure
277283 snotes = spart .iter_all (score .Note , m .start , m .end , include_subclasses = True )
278284 # Beginning of each measure
279-
280285 for snote in snotes :
281286 onset_divs , offset_divs = snote .start .t , snote .start .t + snote .duration_tied
282287 duration_divs = offset_divs - onset_divs
@@ -324,6 +329,15 @@ def matchfile_from_alignment(
324329 if fermata is not None :
325330 score_attributes_list .append ("fermata" )
326331
332+ if isinstance (snote , score .GraceNote ):
333+ score_attributes_list .append ("grace" )
334+
335+ if (
336+ diff_score_version_notes is not None
337+ and snote .id in diff_score_version_notes
338+ ):
339+ score_attributes_list .append ("diff_score_version" )
340+
327341 score_info [snote .id ] = MatchSnote (
328342 version = version ,
329343 anchor = str (snote .id ),
@@ -346,6 +360,22 @@ def matchfile_from_alignment(
346360 )
347361 snote_sort_info [snote .id ] = (onset_beats , snote .doc_order )
348362
363+ # # NOTE time position is hardcoded, not pretty... Assumes there is only one tempo indication at the beginning of the score
364+ if tempo_indication is not None :
365+ score_tempo_direction_header = make_scoreprop (
366+ version = version ,
367+ attribute = "tempoIndication" ,
368+ value = MatchTempoIndication (
369+ tempo_indication ,
370+ is_list = False ,
371+ ),
372+ measure = measure_starts [0 ][0 ],
373+ beat = 1 ,
374+ offset = 0 ,
375+ time_in_beats = measure_starts [0 ][2 ],
376+ )
377+ scoreprop_lines ["tempo_indication" ].append (score_tempo_direction_header )
378+
349379 perf_info = dict ()
350380 pnote_sort_info = dict ()
351381 for pnote in ppart .notes :
@@ -372,6 +402,21 @@ def matchfile_from_alignment(
372402
373403 sort_stime = []
374404 note_lines = []
405+
406+ # Get ids of notes which voice overlap
407+ sna = spart .note_array ()
408+ onset_pitch_slice = sna [["onset_div" , "pitch" ]]
409+ uniques , counts = np .unique (onset_pitch_slice , return_counts = True )
410+ duplicate_values = uniques [counts > 1 ]
411+ duplicates = dict ()
412+ for v in duplicate_values :
413+ idx = np .where (onset_pitch_slice == v )[0 ]
414+ duplicates [tuple (v )] = idx
415+ voice_overlap_note_ids = []
416+ if len (duplicates ) > 0 :
417+ duplicate_idx = np .concatenate (np .array (list (duplicates .values ()))).flatten ()
418+ voice_overlap_note_ids = list (sna [duplicate_idx ]["id" ])
419+
375420 for al_note in alignment :
376421 label = al_note ["label" ]
377422
@@ -384,6 +429,8 @@ def matchfile_from_alignment(
384429
385430 elif label == "deletion" :
386431 snote = score_info [al_note ["score_id" ]]
432+ if al_note ["score_id" ] in voice_overlap_note_ids :
433+ snote .ScoreAttributesList .append ("voice_overlap" )
387434 deletion_line = MatchSnoteDeletion (version = version , snote = snote )
388435 note_lines .append (deletion_line )
389436 sort_stime .append (snote_sort_info [al_note ["score_id" ]])
@@ -441,6 +488,7 @@ def matchfile_from_alignment(
441488 "clock_rate" ,
442489 "key_signatures" ,
443490 "time_signatures" ,
491+ "tempo_indication" ,
444492 ]
445493 all_match_lines = []
446494 for h in header_order :
@@ -537,7 +585,7 @@ def save_match(
537585 else :
538586 raise ValueError (
539587 "`performance_data` should be a `Performance`, a `PerformedPart`, or a "
540- f"list of `PerformedPart` objects, but is { type (score_data )} "
588+ f"list of `PerformedPart` objects, but is { type (performance_data )} "
541589 )
542590
543591 # Get matchfile
0 commit comments