def get_unique_seq(onsets, offsets, unique_onset_idxs=None, return_diff=False): """ Get unique onsets of a sequence of notes """ eps = np.finfo(np.float32).eps first_time = np.min(onsets) # ensure last score time is later than last onset last_time = max(np.max(onsets) + eps, np.max(offsets)) total_dur = last_time - first_time if unique_onset_idxs is None: # unique_onset_idxs = unique_onset_idx(score[:, 0]) unique_onset_idxs = get_unique_onset_idxs(onsets) u_onset = np.array([np.mean(onsets[uix]) for uix in unique_onset_idxs]) # add last offset, so we have as many IOIs as notes u_onset = np.r_[u_onset, last_time] output_dict = dict(u_onset=u_onset, total_dur=total_dur, unique_onset_idxs=unique_onset_idxs) if return_diff: output_dict['diff_u_onset'] = np.diff(u_onset) return output_dict
def encode(self, pitches, velocities, score_onsets, score_durations, unique_onset_idxs=None, return_u_onset_idx=False, *args, **kwargs): if unique_onset_idxs is None: unique_onset_idxs = get_unique_onset_idxs(score_onsets) velocity_trend = np.array( [np.max(velocities[uix]) for uix in unique_onset_idxs]) parameters = np.zeros(len(pitches), dtype=[(pn, 'f4') for pn in self.parameter_names]) for i, jj in enumerate(unique_onset_idxs): parameters['velocity_trend'][jj] = velocity_trend[i] / 127.0 parameters['velocity_dev'][jj] = (velocity_trend[i] - velocities[jj]) / 127.0 if return_u_onset_idx: return parameters, unique_onset_idxs else: return parameters
def decode(self, parameters, *args, **kwargs): score_onsets = kwargs.get('score_onsets', None) if score_onsets is None: velocity = parameters['velocity_trend'] - parameters['velocity_dev'] else: unique_onset_idxs = get_unique_onset_idxs(score_onsets) velocity = parameters['velocity_trend'] vel_dev = parameters['velocity_dev'] for uix in unique_onset_idxs: vd = vel_dev[uix] vd[vd.argmin()] = 0 velocity[uix] -= vd return np.round(velocity * 127.0)
def predict(self, x, score_onsets): if x.ndim != 2: raise ValueError('The inputs should be a 2D array') unique_onset_idxs = get_unique_onset_idxs(score_onsets) _predictions = [] for bf_idxs, model in zip(self.model_bf_idxs, self.models): # Get slice of the input corresponding to the bfs # used in the model model_input = x[:, bf_idxs] # aggregate bfs per onset if model.input_type == 'onsetwise': model_input = notewise_to_onsetwise(model_input, unique_onset_idxs) # make predictions preds = model.predict(model_input) # expand predictions per each note if model.input_type == 'onsetwise': preds = onsetwise_to_notewise(preds, unique_onset_idxs) _predictions.append(preds) # structured array for holding expressive parameters predictions = np.zeros(len(score_onsets), dtype=[(pn, 'f4') for pn in self.output_names]) # assign predictions according to the overlapping strategy # or default value for pn in self.output_names: model_idxs = self.model_param_idxs[pn] if len(model_idxs) > 0: if self.overlapping_output_strategy == 'FIFO': predictions[pn] = _predictions[model_idxs[0]][pn] elif self.overlapping_output_strategy == 'mean': predictions[pn] = np.mean( np.column_stack([_predictions[mix][pn] for mix in model_idxs]), axis=1) else: predictions[pn] = np.ones(len(score_onsets)) * self.default_values[pn] return predictions
def tempo_by_derivative(score_onsets, performed_onsets, score_durations, performed_durations, unique_onset_idxs=None, input_onsets=None, return_onset_idxs=False): """ Computes a tempo curve using the derivative of the average performed onset times of all notes belonging to the same score onset with respect to that score onset. This results in a curve that is smoother than the tempo estimated using `tempo_by_average`. Parameters ---------- score_onsets : np.ndarray Onset in beats of each note in the score. performed_onsets : np.ndarray Performed onsets in seconds of each note in the score. score_durations : np.ndarray Duration in beats of each note in the score. performed_durations : np.ndarray Performed duration in seconds of each note in the score. unique_onset_idxs : np.ndarray or None (optional) Indices of the notes with the same score onset. (By default is None, and is therefore, inferred from `score_onsets`). input_onsets : np.ndarray or None Input onset times in beats at which the tempo curve is to be sampled (by default is None, which means that the tempo curve is returned for each unique score onset) return_onset_idxs : bool Return the indices of the unique score onsets (Default is False) Returns ------- tempo_curve : np.ndarray Tempo curve in seconds per beat (spb). If `input_onsets` was provided, this array contains the value of the tempo in spb for each onset in `input_onsets`. Otherwise, this array contains the value of the tempo in spb for each unique score onset. input_onsets : np.ndarray The score onsets corresponding to each value of the tempo curve. unique_onset_idxs: list Each element of the list is an array of the indices of the score corresponding to the elements in `tempo_curve`. Only returned if `return_onset_idxs` is True. """ # use float64, float32 led to problems that x == x + eps evaluated # to True score_onsets = np.array(score_onsets).astype(np.float64, copy=False) performed_onsets = np.array(performed_onsets).astype(np.float64, copy=False) score_durations = np.array(score_durations).astype(np.float64, copy=False) performed_durations = np.array(performed_durations).astype(np.float64, copy=False) # Get unique onsets if no provided if unique_onset_idxs is None: # Get indices of the unique onsets (quantize score onsets) unique_onset_idxs = get_unique_onset_idxs( (1e4 * score_onsets).astype(np.int)) # Get score information score_info = get_unique_seq(onsets=score_onsets, offsets=score_onsets + score_durations, unique_onset_idxs=unique_onset_idxs, return_diff=False) # Get performance information perf_info = get_unique_seq(onsets=performed_onsets, offsets=performed_onsets + performed_durations, unique_onset_idxs=unique_onset_idxs, return_diff=False) # unique score onsets unique_s_onsets = score_info['u_onset'] # equivalent onsets eq_onsets = perf_info['u_onset'] # Monotonize times eq_onset_mt, unique_s_onsets_mt = monotonize_times(eq_onsets, deltas=unique_s_onsets) # Function that that interpolates the equivalent performed onsets # as a function of the score onset. onset_fun = interp1d(unique_s_onsets_mt, eq_onset_mt, kind='linear', fill_value='extrapolate') if input_onsets is None: input_onsets = unique_s_onsets[:-1] tempo_curve = derivative(onset_fun, input_onsets, dx=0.5) output = [tempo_curve, input_onsets] if return_onset_idxs: output.append(unique_onset_idxs) return output
def tempo_by_average(score_onsets, performed_onsets, score_durations, performed_durations, unique_onset_idxs=None, input_onsets=None, return_onset_idxs=False): """ Computes a tempo curve using the average of the onset times of all notes belonging to the same score onset. Parameters ---------- score_onsets : np.ndarray Onset in beats of each note in the score. performed_onsets : np.ndarray Performed onsets in seconds of each note in the score. score_durations : np.ndarray Duration in beats of each note in the score. performed_durations : np.ndarray Performed duration in seconds of each note in the score. unique_onset_idxs : np.ndarray or None (optional) Indices of the notes with the same score onset. (By default is None, and is therefore, inferred from `score_onsets`). input_onsets : np.ndarray or None Input onset times in beats at which the tempo curve is to be sampled (by default is None, which means that the tempo curve is returned for each unique score onset) return_onset_idxs : bool Return the indices of the unique score onsets (Default is False) Returns ------- tempo_curve : np.ndarray Tempo curve in seconds per beat (spb). If `input_onsets` was provided, this array contains the value of the tempo in spb for each onset in `input_onsets`. Otherwise, this array contains the value of the tempo in spb for each unique score onset. input_onsets : np.ndarray The score onsets corresponding to each value of the tempo curve. unique_onset_idxs: list Each element of the list is an array of the indices of the score corresponding to the elements in `tempo_curve`. Only returned if `return_onset_idxs` is True. """ # use float64, float32 led to problems that x == x + eps evaluated # to True score_onsets = np.array(score_onsets).astype(np.float64, copy=False) performed_onsets = np.array(performed_onsets).astype(np.float64, copy=False) score_durations = np.array(score_durations).astype(np.float64, copy=False) performed_durations = np.array(performed_durations).astype(np.float64, copy=False) # Get unique onsets if no provided if unique_onset_idxs is None: # Get indices of the unique onsets (quantize score onsets) unique_onset_idxs = get_unique_onset_idxs( (1e4 * score_onsets).astype(np.int)) # Get score information score_info = get_unique_seq(onsets=score_onsets, offsets=score_onsets + score_durations, unique_onset_idxs=unique_onset_idxs, return_diff=False) # Get performance information perf_info = get_unique_seq(onsets=performed_onsets, offsets=performed_onsets + performed_durations, unique_onset_idxs=unique_onset_idxs, return_diff=False) # unique score onsets unique_s_onsets = score_info['u_onset'] # equivalent onsets eq_onsets = perf_info['u_onset'] # Monotonize times eq_onset_mt, unique_s_onsets_mt = monotonize_times(eq_onsets, deltas=unique_s_onsets) # Estimate Beat Period perf_iois = np.diff(eq_onset_mt) s_iois = np.diff(unique_s_onsets_mt) beat_period = perf_iois / s_iois tempo_fun = interp1d(unique_s_onsets_mt[:-1], beat_period, kind='zero', bounds_error=False, fill_value=(beat_period[0], beat_period[-1])) if input_onsets is None: input_onsets = unique_s_onsets[:-1] tempo_curve = tempo_fun(input_onsets) if return_onset_idxs: return tempo_curve, input_onsets, unique_onset_idxs else: return tempo_curve, input_onsets