Spaces:
Sleeping
Sleeping
"""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() |