Source code for synctoolbox.feature.dlnco

import matplotlib.pyplot as plt
import numpy as np
from libfmp.b import MultiplePlotsWithColorbar, plot_chromagram, plot_matrix


[docs]def pitch_onset_features_to_DLNCO(f_peaks: dict, feature_sequence_length: int, feature_rate: int = 50, midi_min: int = 21, midi_max: int = 108, log_compression_gamma: float = 10000.0, chroma_norm_ord: int = 2, LN_maxfilterlength_seconds: float = 0.8, LN_maxfilterthresh: float = 0.1, DLNCO_filtercoef: np.ndarray = np.sqrt(1 / np.arange(1, 11)), visualize=False) -> np.ndarray: """Computes decaying locally adaptive normalized chroma onset (DLNCO) features from a dictionary of peaks obtained e.g. by ``audio_to_pitch_onset_features``. Parameters ---------- f_peaks : dict A dictionary of onset peaks * Each key corresponds to the midi pitch number * Each value f_peaks[midi_pitch] is an array of doubles of size 2xN: + First row give the positions of the peaks in milliseconds. + Second row contains the corresponding magnitudes of the peaks. feature_sequence_length : int Desired length of the resulting feature sequence. This should be at least as long as the position of the last peak in ``f_peaks``, but can be longer. feature_rate : int Desired features per second in the output representation midi_min : int Minimum MIDI pitch index (default: 21) midi_max : int Maximum MIDI pitch index (default: 108) log_compression_gamma : float Gamma factor of the log compression applied to peak magnitudes. chroma_norm_ord : int Order of the norm used for chroma onset vectors. LN_maxfilterlength_seconds : float Length of the maximum filter applied for determining local norm of chroma onsets in seconds. LN_maxfilterthresh : float Minimum threshold for normalizing chroma onsets using local norm. DLNCO_filtercoef : np.ndarray Sequence of decay coefficients applied on normalized chroma onsets. visualize : bool Set `True` to visualize chroma onset features (Default: False) Returns ------- f_DLNCO : np.array [shape=(d_dlnco, N_dlnco)] Decaying Locally adaptively Normalized Chroma Onset features """ f_CO = np.zeros((feature_sequence_length, 12)) for midi_pitch in range(midi_min, midi_max + 1): if midi_pitch not in f_peaks: continue time_peaks = f_peaks[midi_pitch][0, :] / 1000 # Now given in seconds val_peaks = np.log(f_peaks[midi_pitch][1, :] * log_compression_gamma + 1) ind_chroma = np.mod(midi_pitch, 12) for k in range(time_peaks.size): indTime = __matlab_round(time_peaks[k] * feature_rate) # Usage of "round" accounts # "center window convention" f_CO[indTime, ind_chroma] += val_peaks[k] # No two ways to normalize F_CO: simply columnwise (f_N) or via local # normalizing curve (f_LN) f_N = np.zeros(feature_sequence_length) for k in range(feature_sequence_length): f_N[k] = np.linalg.norm(f_CO[k, :], chroma_norm_ord) f_LN = np.array(f_N, copy=True) f_left = np.array(f_N, copy=True) f_right = np.array(f_N, copy=True) LN_maxfilterlength_frames = int(LN_maxfilterlength_seconds * feature_rate) if LN_maxfilterlength_frames % 2 == 1: LN_maxfilterlength_frames -= 1 shift = int(np.floor((LN_maxfilterlength_frames) / 2)) # TODO improve with scipy.ndimage.maximum_filter for s in range(shift): f_left = np.roll(f_left, 1, axis=0) f_left[0] = 0 f_right = np.roll(f_right, -1, axis=0) f_right[-1] = 0 f_LN = np.max([f_left, f_LN, f_right], axis=0) f_LN = np.maximum(f_LN, LN_maxfilterthresh) # Compute f_NC0 (normalizing f_C0 using f_N) # f_NCO = np.zeros((feature_sequence_length, 12)) # Compute f_LNC0 (normalizing f_C0 using f_LN) f_LNCO = np.zeros((feature_sequence_length, 12)) for k in range(feature_sequence_length): # f_NCO[k, :] = f_CO[k, :] / (f_N[k]) #+ eps) f_LNCO[k, :] = f_CO[k, :] / f_LN[k] # Compute f_DLNCO f_DLNCO = np.zeros((feature_sequence_length, 12)) num_coef = DLNCO_filtercoef.size for p_idx in range(12): v_shift = np.array(f_LNCO[:, p_idx], copy=True) v_help = np.zeros((feature_sequence_length, num_coef)) for n in range(num_coef): v_help[:, n] = DLNCO_filtercoef[n] * v_shift v_shift = np.roll(v_shift, 1) v_shift[0] = 0 f_DLNCO[:, p_idx] = np.max(v_help, axis=1) # visualization if visualize: plot_chromagram(X=f_CO.T, title='CO', colorbar=True, Fs=feature_rate, colorbar_aspect=50, figsize=(9, 3)) __visualize_LN_features(f_N, f_LN, feature_sequence_length, feature_rate) plot_chromagram(X=f_LNCO.T, title='LNCO', colorbar=True, Fs=feature_rate, colorbar_aspect=50, figsize=(9, 3)) plot_chromagram(X=f_DLNCO.T, title='DLNCO', colorbar=True, Fs=feature_rate, colorbar_aspect=50, figsize=(9, 3)) f_DLNCO = f_DLNCO.T return f_DLNCO
def __visualize_LN_features(f_N: np.ndarray, f_LN: np.ndarray, num_feature: int, res: int, ax: plt.Axes = None): if ax is None: fig, ax = plt.subplots(1, 1, figsize=(9, 3), dpi=72) t = np.arange(0, num_feature) / res ax.plot(t, f_N) if t[-1] > t[0]: ax.set_xlim([t[0], t[-1]]) ax.plot(t, f_LN, 'r') ax.set_title('Local Norm of CO') ax.set_xlabel('Time (seconds)') ax.set_ylabel('Norm') def __matlab_round(x: float = None) -> int: """Workaround to cope the rounding differences between MATLAB and python""" if x - np.floor(x) < 0.5: return int(np.floor(x)) else: return int(np.ceil(x))