Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions mir_eval/multipitch.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,14 @@ def resample_multipitch(times, frequencies, target_times):
if times.size == 0:
return [np.array([])]*len(target_times)

# Warn when the delta between the original times is not constant
if not np.allclose(np.diff(times), np.diff(times).mean()):
warnings.warn(
"Non-uniform timescale passed to resample_multipitch. Pitch "
"will be nearest-neighbor interpolated, which will result in "
"undesirable behavior if silences are indicated by missing values."
" Silences should be indicated by nonpositive frequency values.")

n_times = len(frequencies)

# scipy's interpolate doesn't handle ragged arrays. Instead, we interpolate
Expand Down
77 changes: 77 additions & 0 deletions mir_eval/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import six

import numpy as np
import warnings


def index_labels(labels, case_sensitive=False):
Expand Down Expand Up @@ -956,3 +957,79 @@ def midi_to_hz(midi):
Frequency/frequencies in Hz corresponding to `midi`
'''
return 440.0 * (2.0 ** ((midi - 69.0)/12.0))


def estimate_hop_length(times):
"""Estimate hop length of a semi-regular but non-uniform series of times.

Parameters
----------
times : ndarray
Array of times corresponding to a time series

Returns
-------
hop_length : number
Estimated hop length (seconds)
"""

# Make sure the times are sorted
times = np.sort(times)

# Estimate the hop in seconds
non_gaps = np.append([False], np.isclose(np.diff(times, n=2), 0))

if not np.sum(non_gaps):
raise ValueError("Time observations are too irregular.")

return np.diff(times)[non_gaps].mean()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think .median() would be more stable, since in theory the spacing should be uniform. Then one or two outliers won't skew the hop size, which will over time propagate the error



def time_series_to_uniform(times, values, hop_length=None, duration=None):
"""Convert a semi-regular time series with gaps into a uniform time series.

Parameters
----------
times : ndarray
Array of times corresponding to a time series
values : ndarray
Observations made at times
hop_length : number or None (optional)
Time interval (seconds) between each observation in the uniform series
duration : number of None
Total length (seconds) of times series

Returns
-------
times : ndarray
Uniform time array
values : ndarray
Observations corresponding to uniform times
"""

if hop_length is None:
# If a hop length is not provided, estimate it and throw a warning
warnings.warn(
"Since hop length is unknown, it will be estimated. This may lead to "
"unwanted behavior if the observation times are sporadic or irregular.")
hop_length = estimate_hop_length(times)

if duration is None:
# Default the duration to the last reported time in the series
duration = times[-1]

# Determine the total number of observations in the uniform time series
num_entries = int(np.ceil(duration / hop_length))

new_values = [np.array([])] * num_entries
new_times = hop_length * np.arange(num_entries)

# Determine which indices the provided observations fall under
idcs = np.round(times / hop_length).astype(int)

# Fill the observed values into their respective locations in the uniform series
for i in range(len(idcs)):
if times[i] < duration:
new_values[idcs[i]] = values[i]

return new_times, new_values