CPJKU / partitura

A python package for handling modern staff notation of music
https://partitura.readthedocs.io
Apache License 2.0
227 stars 15 forks source link

Does pt.save_match work? #358

Closed xavriley closed 1 month ago

xavriley commented 1 month ago

I have two MIDI files - one is performance midi and one is score midi.

After aligning them with parangonar the alignment seems to succeed and shows that the notes are mostly matched.

However I can't seem to run pt.save_match() successfully. It fails with the following error:

  File "/Users/xavriley/anaconda3/lib/python3.9/site-packages/partitura/io/exportmatch.py", line 595, in save_match
    matchfile = matchfile_from_alignment(
  File "/Users/xavriley/anaconda3/lib/python3.9/site-packages/partitura/utils/misc.py", line 88, in wrapper
    return f(*args, **kwargs)
  File "/Users/xavriley/anaconda3/lib/python3.9/site-packages/partitura/io/exportmatch.py", line 184, in matchfile_from_alignment
    ptime_to_stime_map, _ = get_time_maps_from_alignment(
  File "/Users/xavriley/anaconda3/lib/python3.9/site-packages/partitura/musicanalysis/performance_codec.py", line 770, in get_time_maps_from_alignment
    [np.mean(perf_onsets[u]) for u in score_unique_onset_idxs]
  File "/Users/xavriley/anaconda3/lib/python3.9/site-packages/partitura/musicanalysis/performance_codec.py", line 770, in <listcomp>
    [np.mean(perf_onsets[u]) for u in score_unique_onset_idxs]
IndexError: arrays used as indices must be of integer (or boolean) type

Stepping through this code it does look like the values in score_unique_onset_idxs are arrays of integers. Am I doing something wrong or is there a bug in this part of the code?

My full code for this is below, along with a link to the file I am using to test.

import parangonar as pa
import partitura as pt
import pretty_midi as pm
from tempfile import NamedTemporaryFile

mid_path = "/Users/xavriley/Dropbox/PhD/Datasets/Filosax/Participant 1/01/Sax.mid"

with NamedTemporaryFile(suffix=".mid") as score_mid_path:
    with NamedTemporaryFile(suffix=".mid") as perf_mid_path:
        mid = pm.PrettyMIDI(mid_path)
        del(mid.instruments[1])
        perf_mid = pm.PrettyMIDI(mid_path)
        del(perf_mid.instruments[0])  
        mid.write(score_mid_path.name)
        perf_mid.write(perf_mid_path.name)
        score = pt.load_score_midi(score_mid_path.name)
        perf = pt.load_performance_midi(perf_mid_path.name)[0]

        score_part = pt.score.unfold_part_maximal(score[0])

        score_na = score_part.note_array(include_grace_notes=True)
        perf_na = perf.note_array()
        sdm = pa.DualDTWNoteMatcher()
        pred_alignment = sdm(score_na, 
                     perf_na,
                     process_ornaments=False,
                     score_part=score_part)
        print(pred_alignment)
        matchfile = pt.io.exportmatch.save_match(pred_alignment, perf, score_part, assume_unfolded=True)

File: https://www.dropbox.com/scl/fi/xyfe3yo7e5ni3e1muf72w/Sax.mid?rlkey=t34r5t0mgjjf2r0os6mvwk5ra&dl=0

sildater commented 1 month ago

Hi @xavriley thanks for the excellent issue including MIDI file! I found the bug in get_time_maps_from_alignment where arrays of idx are cast to numpy dtype objects if all score and performance idx are unique. I created a PR to the develop branch here: #360 with these changes, save_match should work again. You may use the "fix 358" branch or the "develop" branch as soon as the PR is merged (for instance clone the branch, navigate to the repo and then install with pip install .). I don't know when the fix will be in a proper release though. Let us know if you encounter any more issues!