"""Audio utils""" import librosa import numpy as np import matplotlib.pyplot as plt def load_audio(audio_path: str, sr: int = None, max_duration: int = 10., start: int = 0, stop: int = None): """Loads audio and pads/trims it to max_duration""" data, sr = librosa.load(audio_path, sr=sr) if stop is not None: start = int(start * sr) stop = int(stop * sr) data = data[start:stop] # Convert to mono if len(data.shape) > 1: data = np.mean(data, axis=1) n_frames = int(max_duration * sr) if len(data) > n_frames: data = data[:n_frames] elif len(data) < n_frames: data = np.pad(data, (0, n_frames - len(data)), "constant") return data, sr # def compute_spectrogram(data: np.ndarray, sr: int): # D = librosa.stft(data) # STFT of y # S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max) # return S_db def compute_spec_freq_mean(S_db: np.ndarray, eps=1e-5): # Compute mean of spectrogram over frequency axis S_db_normalized = (S_db - S_db.mean(axis=1)[:, None]) / (S_db.std(axis=1)[:, None] + eps) S_db_over_time = S_db_normalized.sum(axis=0) return S_db_over_time def process_audiofile(audio_path, functions=["load_audio", "compute_spectrogram", "compute_spec_freq_mean"]): """Processes audio file with a list of functions""" data, sr = load_audio(audio_path) for function in functions: if function == "load_audio": pass elif function == "compute_spectrogram": data = compute_spectrogram(data, sr) elif function == "compute_spec_freq_mean": data = compute_spec_freq_mean(data) else: raise ValueError(f"Unknown function {function}") return data """PyDub's silence detection is based on the energy of the audio signal.""" import numpy as np def sigmoid(x): return 1 / (1 + np.exp(-x)) class SilenceDetector: def __init__(self, silence_thresh=-36) -> None: self.silence_thresh = silence_thresh def __call__(self, audio_path: str, start=None, end=None): import pydub from pydub.utils import db_to_float try: waveform = pydub.AudioSegment.from_file(audio_path) except: print("Error loading audio file: ", audio_path) return 100.0 start_ms = int(start * 1000) if start else 0 end_ms = int(end * 1000) if end else len(waveform) waveform = waveform[start_ms:end_ms] # convert silence threshold to a float value (so we can compare it to rms) silence_thresh = db_to_float(self.silence_thresh) * waveform.max_possible_amplitude if waveform.rms == 0: return 100.0 silence_prob = sigmoid((silence_thresh - waveform.rms) / waveform.rms) # return waveform.rms <= silence_thresh return np.round(100 * silence_prob, 2) def frequency_bin_to_value(bin_index, sr, n_fft): return int(bin_index * sr / n_fft) def time_bin_to_value(bin_index, hop_length, sr): return (bin_index) * (hop_length / sr) def add_time_annotations(ax, nt_bins, hop_length, sr, skip=50): # Show time (s) values on the x-axis t_bins = np.arange(nt_bins) t_vals = np.round(np.array([time_bin_to_value(tb, hop_length, sr) for tb in t_bins]), 1) try: ax.set_xticks(t_bins[::skip], t_vals[::skip]) except: pass ax.set_xlabel("Time (s)") def add_freq_annotations(ax, nf_bins, sr, n_fft, skip=50): f_bins = np.arange(nf_bins) f_vals = np.array([frequency_bin_to_value(fb, sr, n_fft) for fb in f_bins]) try: ax.set_yticks(f_bins[::skip], f_vals[::skip]) except: pass # ax.set_yticks(f_bins[::skip]) # ax.set_yticklabels(f_vals[::skip]) ax.set_ylabel("Frequency (Hz)") def show_single_spectrogram( spec, sr, n_fft, hop_length, ax=None, fig=None, figsize=(10, 2), cmap="viridis", colorbar=True, show=True, format='%+2.0f dB', xlabel='Time (s)', ylabel="Frequency (Hz)", title=None, show_dom_freq=False, ): if ax is None: fig, ax = plt.subplots(1, 1, figsize=figsize) axim = ax.imshow(spec, origin="lower", cmap=cmap) # Show frequency (Hz) values on y-axis nf_bins, nt_bins = spec.shape if "frequency" in ylabel.lower(): # Add frequency annotation add_freq_annotations(ax, nf_bins, sr, n_fft) # Add time annotation add_time_annotations(ax, nt_bins, hop_length, sr) ax.set_title(title) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) if colorbar: fig.colorbar(axim, ax=ax, orientation='vertical', fraction=0.01, format=format) if show_dom_freq: fmax = spec.argmax(axis=0) ax.scatter(np.arange(spec.shape[1]), fmax, color="white", s=0.2) if show: plt.show() def compute_spectrogram(y, n_fft, hop_length, margin, n_mels=None): # STFT D = librosa.stft(y, n_fft=n_fft, hop_length=hop_length) # Run HPSS S, _ = librosa.decompose.hpss(D, margin=margin) # DB S = librosa.amplitude_to_db(np.abs(S), ref=np.max) if n_mels is not None: S = librosa.feature.melspectrogram(S=S, n_mels=n_mels) return S def show_spectrogram(S, sr, n_fft=512, hop_length=256, figsize=(10, 3), n_mels=None, ax=None, show=True): if ax is None: fig, ax = plt.subplots(1, 1, figsize=figsize) y_axis = "mel" if n_mels is not None else "linear" librosa.display.specshow( S, sr=sr, hop_length=hop_length, n_fft=n_fft, y_axis=y_axis, x_axis='time', ax=ax, ) ax.set_title("LogSpectrogram" if n_mels is None else "LogMelSpectrogram") if show: plt.show() def show_frame_and_spectrogram(frame, S, sr, figsize=(12, 4), show=True, axes=None, **spec_args): if axes is None: fig, axes = plt.subplots(1, 2, figsize=figsize, gridspec_kw={"width_ratios": [0.2, 0.8]}) ax = axes[0] ax.imshow(frame) ax.set_xticks([]) ax.set_yticks([]) ax = axes[1] show_spectrogram(S=S, sr=sr, ax=ax, show=False, **spec_args) plt.tight_layout() if show: plt.show()