Source code for partitura.io.importnakamura

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module contains methods for parsing score-to-performance alignments
in Nakamura et al.'s [1]_ format.

References
----------
.. [1] Nakamura, E., Yoshii, K. and Katayose, H. (2017) "Performance Error
       Detection and Post-Processing for Fast and Accurate Symbolic Music
       Alignment"
"""
import warnings
import re
import numpy as np

from typing import Union, Tuple

from partitura.utils import note_name_to_midi_pitch
from partitura.utils.music import SIGN_TO_ALTER

from partitura.utils.misc import PathLike, deprecated_alias


NAME_PATT = re.compile(r"([A-G]{1})([xb\#]*)(\d+)")


[docs]@deprecated_alias(fn="filename") def load_nakamuracorresp(filename: PathLike) -> Tuple[Union[np.ndarray, list]]: """Load a corresp file as returned by Nakamura et al.'s MIDI to MIDI alignment. Fields of the file format as specified in [8]_: (ID) (onset time) (spelled pitch) (integer pitch) (onset velocity) Parameters ---------- filename : str The nakamura match.txt-file Returns ------- align : structured array structured array of performed notes ref : structured array structured array of score notes alignment : list The score--performance alignment, a list of dictionaries """ note_array_dtype = [("onset_sec", "f4"), ("pitch", "i4"), ("id", "U256")] dtype = [ ("alignID", "U256"), ("alignOntime", "f"), ("alignSitch", "U256"), ("alignPitch", "i"), ("alignOnvel", "i"), ("refID", "U256"), ("refOntime", "f"), ("refSitch", "U256"), ("refPitch", "i"), ("refOnvel", "i"), ] result = np.loadtxt(filename, dtype=dtype, comments="//") align_valid = result["alignID"] != "*" n_align = sum(align_valid) align = np.empty((n_align,), dtype=note_array_dtype) align[:] = result[["alignOntime", "alignPitch", "alignID"]][align_valid] ref_valid = result["refID"] != "*" n_ref = sum(ref_valid) ref = np.empty((n_ref,), dtype=note_array_dtype) ref[:] = result[["refOntime", "refPitch", "refID"]][ref_valid] alignment = [] for alignID, refID in result[["alignID", "refID"]]: if alignID == "*": alnote = dict(label="deletion", score_id=refID) elif refID == "*": alnote = dict(label="insertion", performance_id=alignID) else: alnote = dict(label="match", score_id=refID, performance_id=alignID) alignment.append(alnote) return align, ref, alignment
[docs]@deprecated_alias(fn="filename") def load_nakamuramatch(filename: PathLike) -> Tuple[Union[np.ndarray, list]]: """Load a match file as returned by Nakamura et al.'s MIDI to musicxml alignment Fields of the file format as specified in [8]_: ID (onset time) (offset time) (spelled pitch) (onset velocity)(offset velocity) channel (match status) (score time) (note ID)(error index) (skip index) Parameters ---------- filename : str The nakamura match.txt-file Returns ------- align : structured array structured array of performed notes ref : structured array structured array of score notes alignment : list The score--performance alignment, a list of dictionaries References ---------- .. [8] https://midialignment.github.io/MANUAL.pdf """ perf_dtype = [ ("onset_sec", "f4"), ("duration_sec", "f4"), ("pitch", "i4"), ("velocity", "i4"), ("channel", "i4"), ("id", "U256"), ] score_dtype = [ ("onset_div", "i4"), ("pitch", "i4"), ("step", "U256"), ("alter", "i4"), ("octave", "i4"), ("id", "U256"), ] dtype = [ ("alignID", "U256"), ("alignOntime", "f"), ("alignOfftime", "f"), ("alignSitch", "U256"), ("alignOnvel", "i"), ("alignOffvel", "i"), ("alignChannel", "i"), ("matchstatus", "i"), ("refOntime", "f"), ("refID", "U256"), ("errorindex", "i"), ("skipindex", "U256"), ] dtype_missing = [("refOntime", "f"), ("refID", "U256")] pattern = r"//Missing\s(\d+)\t(.+)" # load alignment notes result = np.loadtxt(filename, dtype=dtype, comments="//") # 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"]] ) align_valid = result["alignID"] != "*" n_align = sum(align_valid) align = np.empty((n_align,), dtype=perf_dtype) align["id"] = result["alignID"][align_valid] align["onset_sec"] = result["alignOntime"] align["duration_sec"] = ( result["alignOfftime"][align_valid] - result["alignOntime"][align_valid] ) align["pitch"] = midi_pitch[align_valid] align["velocity"] = result["alignOnvel"][align_valid] align["channel"] = result["alignChannel"][align_valid] ref_valid = result["refID"] != "*" n_valid = sum(ref_valid) n_ref = n_valid + len(missing) ref = np.empty((n_ref,), dtype=score_dtype) ref["id"][:n_valid] = result["refID"][ref_valid] ref["id"][n_valid:] = missing["refID"] ref["onset_div"][:n_valid] = result["refOntime"][ref_valid] ref["onset_div"][n_valid:] = missing["refOntime"] ref["pitch"][:n_valid] = midi_pitch[ref_valid] ref["pitch"][n_valid:] = -1 pitch_spelling = [NAME_PATT.search(nn).groups() for nn in result["alignSitch"]] pitch_spelling = np.array( [ (ps[0], SIGN_TO_ALTER[ps[1] if ps[1] != "" else "n"], int(ps[2])) for ps in pitch_spelling ] ) # add pitch spelling information ref["step"][:n_valid] = pitch_spelling[ref_valid][:, 0] ref["alter"][:n_valid] = pitch_spelling[ref_valid][:, 1] ref["octave"][:n_valid] = pitch_spelling[ref_valid][:, 2] # * indicates that is a missing pitch ref["step"][n_valid:] = "*" alignment = [] for alignID, refID in result[["alignID", "refID"]]: if alignID == "*": alnote = dict(label="deletion", score_id=refID) elif refID == "*": alnote = dict(label="insertion", performance_id=alignID) else: alnote = dict(label="match", score_id=refID, performance_id=alignID) alignment.append(alnote) for refID in missing["refID"]: alignment.append(dict(label="deletion", score_id=refID)) return align, ref, alignment
@deprecated_alias(fn="filename") def load_nakamuraspr(filename: PathLike) -> np.ndarray: """Load a spr file as returned by Nakamura et al.'s alignment methods. Fields of the file format as specified in [8]_: ID (onset time) (offset time) (spelled pitch) (onset velocity) (offset velocity) channel These files contain extra information not included in match or corresp files, particularly duration and pedal information, and can be used to complement the information from the `load_nakamuracorresp` or `load_nakamuramatch`. Parameters ---------- filename : str The nakamura match.txt-file Returns ------- note_array : structured array structured array with note information References ---------- .. [8] https://midialignment.github.io/MANUAL.pdf TODO ---- * Import pedal information """ note_array_dtype = [ ("onset_sec", "f4"), ("duration_sec", "f4"), ("pitch", "i4"), ("velocity", "i4"), ("channel", "i4"), ("id", "U256"), ] dtype = [ ("ID", "U256"), ("Ontime", "f"), ("Offtime", "f"), ("Sitch", "U256"), ("Onvel", "i"), ("Offvel", "i"), ("Channel", "i"), ] pattern = r"(\d+)\t(.+)\t(.+)\t(.+)\t(.+)\t(.+)\t(.+)" result = np.fromregex(filename, pattern, dtype=dtype) note_array = np.empty(len(result), dtype=note_array_dtype) note_array["id"] = result["ID"] note_array["onset_sec"] = result["Ontime"] note_array["duration_sec"] = result["Offtime"] - result["Ontime"] note_array["pitch"] = np.array( [note_name_to_midi_pitch(n) for n in result["Sitch"]] ) note_array["velocity"] = result["Onvel"] note_array["channel"] = result["Channel"] return note_array