"""mirdata annotation data types
"""
import numpy as np
[docs]class Annotation(object):
"""Annotation base class"""
def __repr__(self):
attributes = [v for v in dir(self) if not v.startswith("_")]
repr_str = f"{self.__class__.__name__}({', '.join(attributes)})"
return repr_str
[docs]class BeatData(Annotation):
"""BeatData class
Attributes:
times (np.ndarray): array of time stamps (as floats) in seconds
with positive, strictly increasing values
positions (np.ndarray or None): array of beat positions (as ints)
e.g. 1, 2, 3, 4
"""
def __init__(self, times, positions=None):
validate_array_like(times, np.ndarray, float)
validate_array_like(positions, np.ndarray, int, none_allowed=True)
validate_lengths_equal([times, positions])
validate_times(times)
self.times = times
self.positions = positions
[docs]class SectionData(Annotation):
"""SectionData class
Attributes:
intervals (np.ndarray): (n x 2) array of intervals
(as floats) in seconds in the form [start_time, end_time]
times should be positive and intervals should have
non-negative duration
labels (list or None): list of labels (as strings)
"""
def __init__(self, intervals, labels=None):
validate_array_like(intervals, np.ndarray, float)
validate_array_like(labels, list, str, none_allowed=True)
validate_lengths_equal([intervals, labels])
validate_intervals(intervals)
self.intervals = intervals
self.labels = labels
[docs]class NoteData(Annotation):
"""NoteData class
Attributes:
intervals (np.ndarray): (n x 2) array of intervals
(as floats) in seconds in the form [start_time, end_time]
with positive time stamps and end_time >= start_time.
notes (np.ndarray): array of notes (as floats) in Hz
confidence (np.ndarray or None): array of confidence values
between 0 and 1
"""
def __init__(self, intervals, notes, confidence=None):
validate_array_like(intervals, np.ndarray, float)
validate_array_like(notes, np.ndarray, float)
validate_array_like(confidence, np.ndarray, float, none_allowed=True)
validate_lengths_equal([intervals, notes, confidence])
validate_intervals(intervals)
validate_confidence(confidence)
self.intervals = intervals
self.notes = notes
self.confidence = confidence
[docs]class ChordData(Annotation):
"""ChordData class
Attributes:
intervals (np.ndarray or None): (n x 2) array of intervals
(as floats) in seconds in the form [start_time, end_time]
with positive time stamps and end_time >= start_time.
labels (list): list chord labels (as strings)
confidence (np.ndarray or None): array of confidence values
between 0 and 1
"""
def __init__(self, intervals, labels, confidence=None):
validate_array_like(intervals, np.ndarray, float)
validate_array_like(labels, list, str)
validate_array_like(confidence, np.ndarray, float, none_allowed=True)
validate_lengths_equal([intervals, labels, confidence])
validate_intervals(intervals)
validate_confidence(confidence)
self.intervals = intervals
self.labels = labels
self.confidence = confidence
[docs]class F0Data(Annotation):
"""F0Data class
Attributes:
times (np.ndarray): array of time stamps (as floats) in seconds
with positive, strictly increasing values
frequencies (np.ndarray): array of frequency values (as floats)
in Hz
confidence (np.ndarray or None): array of confidence values
between 0 and 1
"""
def __init__(self, times, frequencies, confidence=None):
validate_array_like(times, np.ndarray, float)
validate_array_like(frequencies, np.ndarray, float)
validate_array_like(confidence, np.ndarray, float, none_allowed=True)
validate_lengths_equal([times, frequencies, confidence])
validate_times(times)
validate_confidence(confidence)
self.times = times
self.frequencies = frequencies
self.confidence = confidence
[docs]class MultiF0Data(Annotation):
"""MultiF0Data class
Attributes:
times (np.ndarray): array of time stamps (as floats) in seconds
with positive, strictly increasing values
frequency_list (list): list of lists of frequency values (as floats)
in Hz
confidence_list (list or None): list of lists of confidence values
between 0 and 1
"""
def __init__(self, times, frequency_list, confidence_list=None):
validate_array_like(times, np.ndarray, float)
validate_array_like(frequency_list, list, list)
validate_array_like(confidence_list, list, list, none_allowed=True)
validate_lengths_equal([times, frequency_list, confidence_list])
validate_times(times)
self.times = times
self.frequency_list = frequency_list
self.confidence_list = confidence_list
[docs]class KeyData(Annotation):
"""KeyData class
Attributes:
intervals (np.ndarray): (n x 2) array of intervals
(as floats) in seconds in the form [start_time, end_time]
with positive time stamps and end_time >= start_time.
keys (list): list key labels (as strings)
"""
def __init__(self, intervals, keys):
validate_array_like(intervals, np.ndarray, float)
validate_array_like(keys, list, str)
validate_lengths_equal([intervals, keys])
validate_intervals(intervals)
self.intervals = intervals
self.keys = keys
[docs]class LyricData(Annotation):
"""LyricData class
Attributes:
intervals (np.ndarray): (n x 2) array of intervals
(as floats) in seconds in the form [start_time, end_time]
with positive time stamps and end_time >= start_time.
lyrics (list): list of lyrics (as strings)
pronunciations (list or None): list of pronunciations (as strings)
"""
def __init__(self, intervals, lyrics, pronunciations=None):
validate_array_like(intervals, np.ndarray, float)
validate_array_like(lyrics, list, str)
validate_array_like(pronunciations, list, str, none_allowed=True)
validate_lengths_equal([intervals, lyrics, pronunciations])
validate_intervals(intervals)
self.intervals = intervals
self.lyrics = lyrics
self.pronunciations = pronunciations
[docs]class TempoData(Annotation):
"""TempoData class
Attributes:
intervals (np.ndarray): (n x 2) array of intervals
(as floats) in seconds in the form [start_time, end_time]
with positive time stamps and end_time >= start_time.
value (list): array of tempo values (as floats)
confidence (np.ndarray or None): array of confidence values
between 0 and 1
"""
def __init__(self, intervals, value, confidence=None):
validate_array_like(intervals, np.ndarray, float)
validate_array_like(value, np.ndarray, float)
validate_array_like(confidence, np.ndarray, float, none_allowed=True)
validate_lengths_equal([intervals, value, confidence])
validate_intervals(intervals)
validate_confidence(confidence)
self.intervals = intervals
self.value = value
self.confidence = confidence
[docs]class EventData(Annotation):
"""TempoData class
Attributes:
intervals (np.ndarray): (n x 2) array of intervals
(as floats) in seconds in the form [start_time, end_time]
with positive time stamps and end_time >= start_time.
events (list): list of event labels (as strings)
"""
def __init__(self, intervals, events):
validate_array_like(intervals, np.ndarray, float)
validate_array_like(events, list, str)
validate_lengths_equal([intervals, events])
validate_intervals(intervals)
self.intervals = intervals
self.events = events
[docs]def validate_array_like(array_like, expected_type, expected_dtype, none_allowed=False):
"""Validate that array-like object is well formed
If array_like is None, validation passes automatically.
Args:
array_like (array-like): object to validate
expected_type (type): expected type, either list or np.ndarray
expected_dtype (type): expected dtype
none_allowed (bool): if True, allows array to be None
Raises:
TypeError: if type/dtype does not match expected_type/expected_dtype
ValueError: if array
"""
if array_like is None:
if none_allowed:
return
else:
raise ValueError("array_like cannot be None")
assert expected_type in [
list,
np.ndarray,
], "expected type must be a list or np.ndarray"
if not isinstance(array_like, expected_type):
raise TypeError(
f"Object should be a {expected_type}, but is a {type(array_like)}"
)
if expected_type == list and not all(
isinstance(n, expected_dtype) for n in array_like
):
raise TypeError(f"List elements should all have type {expected_dtype}")
if expected_type == np.ndarray and array_like.dtype != expected_dtype:
raise TypeError(
f"Array should have dtype {expected_dtype} but has {array_like.dtype}"
)
if np.asarray(array_like).size == 0:
raise ValueError("Object should not be empty, use None instead")
[docs]def validate_lengths_equal(array_list):
"""Validate that arrays in list are equal in length
Some arrays may be None, and the validation for these are skipped.
Args:
array_list (list): list of array-like objects
Raises:
ValueError: if arrays are not equal in length
"""
if len(array_list) == 1:
return
for att1, att2 in zip(array_list[:1], array_list[1:]):
if att1 is None or att2 is None:
continue
if not len(att1) == len(att2):
raise ValueError("Arrays have unequal length")
[docs]def validate_confidence(confidence):
"""Validate if confidence is well-formed.
If confidence is None, validation passes automatically
Args:
confidence (np.ndarray): an array of confidence values
Raises:
ValueError: if confidence are not between 0 and 1
"""
if confidence is None:
return
confidence_shape = np.shape(confidence)
if len(confidence_shape) != 1:
raise ValueError(
f"Confidence should be 1d, but array has shape {confidence_shape}"
)
if (confidence < 0).any() or (confidence > 1).any():
raise ValueError("confidence should be between 0 and 1")
[docs]def validate_times(times):
"""Validate if times are well-formed.
If times is None, validation passes automatically
Args:
times (np.ndarray): an array of time stamps
Raises:
ValueError: if times have negative values or are non-increasing
"""
if times is None:
return
time_shape = np.shape(times)
if len(time_shape) != 1:
raise ValueError(f"Times should be 1d, but array has shape {time_shape}")
if (times < 0).any():
raise ValueError("times should be positive numbers")
if (times[1:] - times[:-1] <= 0).any():
raise ValueError("times should be strictly increasing")
[docs]def validate_intervals(intervals):
"""Validate if intervals are well-formed.
If intervals is None, validation passes automatically
Args:
intervals (np.ndarray): (n x 2) array
Raises:
ValueError: if intervals have an invalid shape, have negative values
or if end times are smaller than start times.
"""
if intervals is None:
return
# validate that intervals have the correct shape
interval_shape = np.shape(intervals)
if len(interval_shape) != 2 or interval_shape[1] != 2:
raise ValueError(
f"Intervals should be arrays with two columns, but array has {interval_shape}"
)
# validate that time stamps are all positive numbers
if (intervals < 0).any():
raise ValueError(f"Interval values should be nonnegative numbers")
# validate that end times are bigger than start times
elif (intervals[:, 1] - intervals[:, 0] < 0).any():
raise ValueError(f"Interval start times must be smaller than end times")