Create audio_utils.py

This commit is contained in:
David Marx
2023-12-31 17:22:22 -08:00
committed by GitHub
parent 2ca202d095
commit 21bbeaad58
+190
View File
@@ -0,0 +1,190 @@
"""
porting useful stuff from vktrs: https://github.com/dmarx/video-killed-the-radio-star/blob/main/Video_Killed_The_Radio_Star_Defusion.ipynb
"""
##############################################################
# audio processing
def analyze_audio_structure(
audio_fpath,
BINS_PER_OCTAVE = 12 * 3, # should be a multiple of twelve: https://github.com/MTG/essentia/blob/master/src/examples/python/tutorial_spectral_constantq-nsg.ipynb
N_OCTAVES = 7,
):
"""
via librosa docs
https://librosa.org/doc/latest/auto_examples/plot_segmentation.html#sphx-glr-auto-examples-plot-segmentation-py
cites: McFee and Ellis, 2014 - https://brianmcfee.net/papers/ismir2014_spectral.pdf
"""
y, sr = librosa.load(audio_fpath)
C = librosa.amplitude_to_db(np.abs(librosa.cqt(y=y, sr=sr,
bins_per_octave=BINS_PER_OCTAVE,
n_bins=N_OCTAVES * BINS_PER_OCTAVE)),
ref=np.max)
# reduce dimensionality via beat-synchronization
tempo, beats = librosa.beat.beat_track(y=y, sr=sr, trim=False)
Csync = librosa.util.sync(C, beats, aggregate=np.median)
# I have concerns about this frame fixing operation
beat_times = librosa.frames_to_time(librosa.util.fix_frames(beats, x_min=0), sr=sr)
# width=3 prevents links within the same bar
# mode=’affinity’ here implements S_rep (after Eq. 8)
R = librosa.segment.recurrence_matrix(Csync, width=3, mode='affinity', sym=True)
# Enhance diagonals with a median filter (Equation 2)
df = librosa.segment.timelag_filter(scipy.ndimage.median_filter)
Rf = df(R, size=(1, 7))
# build the sequence matrix (S_loc) using mfcc-similarity
mfcc = librosa.feature.mfcc(y=y, sr=sr)
Msync = librosa.util.sync(mfcc, beats)
path_distance = np.sum(np.diff(Msync, axis=1)**2, axis=0)
sigma = np.median(path_distance)
path_sim = np.exp(-path_distance / sigma)
R_path = np.diag(path_sim, k=1) + np.diag(path_sim, k=-1)
# compute the balanced combination
deg_path = np.sum(R_path, axis=1)
deg_rec = np.sum(Rf, axis=1)
mu = deg_path.dot(deg_path + deg_rec) / np.sum((deg_path + deg_rec)**2)
A = mu * Rf + (1 - mu) * R_path
# compute normalized laplacian and its spectrum
L = scipy.sparse.csgraph.laplacian(A, normed=True)
evals, evecs = scipy.linalg.eigh(L)
# clean this up with a median filter. can help smooth over discontinuities
evecs = scipy.ndimage.median_filter(evecs, size=(9, 1))
return dict(
y=y,
sr=np.array(sr).astype(np.uint32),
tempo=tempo,
beats=beats,
beat_times=beat_times,
evecs=evecs,
)
def laplacian_segmentation(
audio_fpath=None,
evecs=None,
n_clusters = 5,
n_spectral_features = None,
):
"""
segment audio by clustering a self-similarity matrix.
via librosa docs
https://librosa.org/doc/latest/auto_examples/plot_segmentation.html#sphx-glr-auto-examples-plot-segmentation-py
cites: McFee and Ellis, 2014 - https://brianmcfee.net/papers/ismir2014_spectral.pdf
"""
if evecs is None:
if audio_fpath is None:
raise Exception("One of `audio_fpath` or `evecs` must be provided")
features = analyze_audio_structure(audio_fpath)
evecs = features['evecs']
if n_clusters < 2:
seg_ids = np.zeros(evecs.shape[0], dtype=int)
return seg_ids
if n_spectral_features is None:
n_spectral_features = n_clusters
# cumulative normalization is needed for symmetric normalize laplacian eigenvectors
Cnorm = np.cumsum(evecs**2, axis=1)**0.5
k = n_spectral_features
X = evecs[:, :k] / Cnorm[:, k-1:k]
# use these k components to cluster beats into segments
KM = sklearn.cluster.KMeans(n_clusters=n_clusters, n_init="auto")
seg_ids = KM.fit_predict(X)
return seg_ids #, beat_times, tempo
# for video duration
def get_audio_duration_seconds(audio_fpath):
outv = subprocess.run([
'ffprobe'
,'-i',audio_fpath
,'-show_entries', 'format=duration'
,'-v','quiet'
,'-of','csv=p=0'
],
stdout=subprocess.PIPE
).stdout.decode('utf-8')
return float(outv.strip())
##########################################################
# audioreactivity stuff
def full_width_plot():
ax = plt.gca()
ax.figure.set_figwidth(20)
plt.show()
def display_signal(y, sr, show_spec=True, title=None, start_time=0, end_time=9999):
# if show_spec:
# frame_time = librosa.samples_to_time(np.arange(len(normalized_signal)), sr=sr)
# else:
# frame_time = librosa.frames_to_time(np.arange(len(normalized_signal)), sr=sr)
if show_spec:
#librosa.display.waveshow(y, sr=sr)
times = librosa.samples_to_time(np.arange(len(y)), sr=sr)
else:
#times = librosa.times_like(y, sr=sr).ravel()
times = librosa.frames_to_time(np.arange(len(y)), sr=sr).ravel()
start_idx = np.argmax(start_time <= times)
#end_idx = len(times) - np.argmax([end_time <= times][::-1])
end_idx = np.argmax(end_time <= times)
if start_idx >= end_idx:
end_idx = -1
times = times[start_idx:end_idx]
y = y[start_idx:end_idx]
plt.plot(times, y)
if title:
plt.title(title)
full_width_plot()
if show_spec:
try:
M = librosa.feature.melspectrogram(y=y, sr=sr)
librosa.display.specshow(librosa.power_to_db(M, ref=np.max),
y_axis='mel', x_axis='time')
full_width_plot()
except:
pass
# plt.plot(frame_time, y)
# if title:
# plt.title(title)
# full_width_plot()
# https://github.com/pytti-tools/pytti-core/blob/9e8568365cfdc123d2d2fbc20d676ca0f8715341/src/pytti/AudioParse.py#L95
from scipy.signal import butter, sosfilt, sosfreqz
def butter_bandpass(lowcut, highcut, fs, order):
nyq = 0.5 * fs
low = lowcut / nyq
high = highcut / nyq
sos = butter(order, [low, high], analog=False, btype='bandpass', output='sos')
return sos
def butter_bandpass_filter(y, sr, lowcut, highcut, order=10):
sos = butter_bandpass(lowcut, highcut, sr, order=order)
y = sosfilt(sos, y)
return y
########################################################################