#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This module contains methods for saving Parangonada csv files
"""
import os
import numpy as np
from typing import Union, List, Iterable, Tuple, Optional
from partitura.score import ScoreLike
from partitura.performance import PerformanceLike, Performance, PerformedPart
from partitura.utils import ensure_notearray
from partitura.utils.misc import PathLike, deprecated_alias
__all__ = [
"save_parangonada_csv",
"save_parangonada_alignment",
]
def alignment_dicts_to_array(alignment: List[dict]) -> np.ndarray:
"""
create structured array from list of dicts type alignment.
Parameters
----------
alignment : list
A list of note alignment dictionaries.
Returns
-------
alignarray : structured ndarray
Structured array containing note alignment.
"""
fields = [
("idx", "i4"),
("matchtype", "U256"),
("partid", "U256"),
("ppartid", "U256"),
]
array = []
# for all dicts create an appropriate entry in an array:
# match = 0, deletion = 1, insertion = 2
for no, i in enumerate(alignment):
if i["label"] == "match":
array.append((no, "0", i["score_id"], str(i["performance_id"])))
elif i["label"] == "insertion":
array.append((no, "2", "undefined", str(i["performance_id"])))
elif i["label"] == "deletion":
array.append((no, "1", i["score_id"], "undefined"))
alignarray = np.array(array, dtype=fields)
return alignarray
[docs]@deprecated_alias(
spart="score_data",
ppart="performance_data",
align="alignment",
)
def save_parangonada_csv(
alignment: List[dict],
performance_data: Union[PerformanceLike, np.ndarray],
score_data: Union[ScoreLike, np.ndarray],
outdir: Optional[PathLike] = None,
zalign: Optional[List[dict]] = None,
feature: Optional[List[dict]] = None,
) -> Optional[Tuple[np.ndarray]]:
"""
Save an alignment for visualization with parangonda.
Parameters
----------
alignment : list
A list of note alignment dictionaries.
performance_data : Performance, PerformedPart, structured ndarray
The performance information
score_data : ScoreLike
The musical score. A :class:`partitura.score.Score` object,
a :class:`partitura.score.Part`, a :class:`partitura.score.PartGroup` or
a list of these.
outdir : PathLike
A directory to save the files into.
ppart : PerformedPart, structured ndarray
A PerformedPart or its note_array.
zalign : list, optional
A second list of note alignment dictionaries.
feature : list, optional
A list of expressive feature dictionaries.
Returns
-------
perf_note_array : np.ndarray
The performance note array. Only returned if `outdir` is None.
score_note_array: np.ndarray
The note array from the score. Only returned if `outdir` is None.
alignarray: np.ndarray
zalignarray: np.ndarray
featurearray: np.ndarray
"""
score_note_array = ensure_notearray(score_data)
perf_note_array = ensure_notearray(performance_data)
valid_score_note_array_fields = [
"onset_beat",
"duration_beat",
"onset_quarter",
"duration_quarter",
"onset_div",
"duration_div",
"pitch",
"voice",
"id",
]
valid_perf_note_array_fields = [
"onset_sec",
"duration_sec",
"pitch",
"velocity",
"track",
"channel",
"id",
]
ffields = [
("velocity", "<f4"),
("timing", "<f4"),
("articulation", "<f4"),
("id", "U256"),
]
farray = []
notes = list(score_note_array["id"])
if feature is not None:
# veloctiy, timing, articulation, note
for no, i in enumerate(list(feature["id"])):
farray.append(
(
feature["velocity"][no],
feature["timing"][no],
feature["articulation"][no],
i,
)
)
else:
for no, i in enumerate(notes):
farray.append((0, 0, 0, i))
featurearray = np.array(farray, dtype=ffields)
alignarray = alignment_dicts_to_array(alignment)
if zalign is not None:
zalignarray = alignment_dicts_to_array(zalign)
else: # if no zalign is available, save the same alignment twice
zalignarray = alignment_dicts_to_array(alignment)
if outdir is not None:
np.savetxt(
os.path.join(outdir, "ppart.csv"),
# outdir + os.path.sep + "perf_note_array.csv",
perf_note_array[valid_perf_note_array_fields],
fmt="%.20s",
delimiter=",",
header=",".join(valid_perf_note_array_fields),
comments="",
)
np.savetxt(
os.path.join(outdir, "part.csv"),
# outdir + os.path.sep + "score_note_array.csv",
score_note_array[valid_score_note_array_fields],
fmt="%.20s",
delimiter=",",
header=",".join(valid_score_note_array_fields),
comments="",
)
np.savetxt(
os.path.join(outdir, "align.csv"),
# outdir + os.path.sep + "align.csv",
alignarray,
fmt="%.20s",
delimiter=",",
header=",".join(alignarray.dtype.names),
comments="",
)
np.savetxt(
os.path.join(outdir, "zalign.csv"),
# outdir + os.path.sep + "zalign.csv",
zalignarray,
fmt="%.20s",
delimiter=",",
header=",".join(zalignarray.dtype.names),
comments="",
)
np.savetxt(
os.path.join(outdir, "feature.csv"),
# outdir + os.path.sep + "feature.csv",
featurearray,
fmt="%.20s",
delimiter=",",
header=",".join(featurearray.dtype.names),
comments="",
)
else:
return (
perf_note_array,
score_note_array,
alignarray,
zalignarray,
featurearray,
)
# alias
save_csv_for_parangonada = save_parangonada_csv
@deprecated_alias(align="alignment", outfile="out")
def save_parangonada_alignment(
alignment: List[dict],
out: Optional[PathLike] = None,
):
"""
Save only an alignment csv for visualization with parangonda.
For score, performance, and expressive features use
save_csv_for_parangonada()
Parameters
----------
align : list
A list of note alignment dictionaries.
outdir : str
A directory to save the files into.
Returns
-------
alignarray : np.ndarray
Array containing the alignment. This array will only be returned if `out`
is not None
"""
alignarray = alignment_dicts_to_array(alignment)
if out is not None:
np.savetxt(
out,
alignarray,
fmt="%.20s",
delimiter=",",
header=",".join(alignarray.dtype.names),
comments="",
)
else:
return alignarray
# alias
save_alignment_for_parangonada = save_parangonada_alignment
@deprecated_alias(outfile="out", ppart="performance_data")
def save_alignment_for_ASAP(
alignment: List[dict],
performance_data: PerformanceLike,
out: PathLike,
) -> None:
"""
load an alignment exported from parangonda.
Parameters
----------
alignment : list
A list of note alignment dictionaries.
performance_data : PerformanceLike
A performance.
out : str
A path for the alignment tsv file.
"""
if isinstance(performance_data, (Performance, Iterable)):
ppart = performance_data[0]
elif isinstance(performance_data, PerformedPart):
ppart = performance_data
notes_indexed_by_id = {
str(n["id"]): [
str(n["id"]),
str(n["track"]),
str(n["channel"]),
str(n["midi_pitch"]),
str(n["note_on"]),
]
for n in ppart.notes
}
with open(out, "w") as f:
f.write("xml_id\tmidi_id\ttrack\tchannel\tpitch\tonset\n")
for line in alignment:
if line["label"] == "match":
outline_score = [str(line["score_id"])]
outline_perf = notes_indexed_by_id[str(line["performance_id"])]
f.write("\t".join(outline_score + outline_perf) + "\n")
elif line["label"] == "deletion":
outline_score = str(line["score_id"])
f.write(outline_score + "\tdeletion\n")
elif line["label"] == "insertion":
outline_score = ["insertion"]
outline_perf = notes_indexed_by_id[str(line["performance_id"])]
f.write("\t".join(outline_score + outline_perf) + "\n")