feat: Segmenter, RegularBeatFinder, SigQuality

This commit is contained in:
2026-04-27 22:13:02 +02:00
parent 5d9de7d8f1
commit 975adcdee4
4 changed files with 301 additions and 0 deletions

63
segmenter.py Normal file
View File

@@ -0,0 +1,63 @@
import numpy as np
from sklearn.cluster import KMeans
def median_filter(a, w):
ap = np.pad(a, (w//2, w//2), mode='edge')
o = np.zeros(a.shape[0])
for i in np.arange(a.shape[0]):
sl = ap[i:i+w]
o[i] = np.median(sl)
return o
class Segmenter:
seg_win_size_sec = 4.0 #: window size for stat. measures for segmentation, in sec
seg_win_step_sec = 1.0 #: step for segmentation, in sec
n_clusters = 8 #: clusters for KMeans algorithm
seg_filt_win_sec = 20.0 #: median filter width for smoothing segments
def __init__(self): pass
def get_segment_boundaries(self, fs, guitar):
"""split the spectral power signal 'guitar' into stochastically similar segments."""
segment_ids = self.get_segments(fs, guitar)
stxs = np.diff(segment_ids) != 0
i_stxs = np.where(stxs)[0]
return i_stxs
def get_segments(self, fs, guitar):
"""split the spectral power signal 'guitar' into stochastically similar segments."""
seg_filt_win = int(self.seg_filt_win_sec / self.seg_win_step_sec)
seg_guitar_data = self._sig_stochastics(fs, guitar)
X = np.vstack((
seg_guitar_data[:,0]*1.4,
np.sqrt(np.sum(seg_guitar_data[:,1:]**2, axis=1))
)).T
# cluster by stochastic characteristics
kmeans = KMeans(n_clusters=self.n_clusters, random_state=0, n_init="auto").fit(X)
segment_ids = np.floor(median_filter(kmeans.labels_, seg_filt_win)).astype(int)
# up-sample segment id assignment
iidx = np.linspace(0, segment_ids.shape[0], guitar.shape[0], endpoint=False).astype(int)
return segment_ids[iidx]
def _sig_stochastics(self, fs, y):
"""compute the stochastic moments of the signal. normalized."""
seg_win_size = int(self.seg_win_size_sec * fs)
seg_win_step = int(self.seg_win_step_sec * fs)
#
seg_y_data = np.zeros((y.shape[0] // seg_win_step, 4))
y_pad = np.pad(y, (seg_win_size // 2, seg_win_size // 2))
y_0_max, y_0_mean = np.max(y), np.mean(y)
y_1_max = np.max(np.mean((y - y_0_mean)**2))
y_2_max = np.max(np.mean(np.abs((y - y_0_mean)**3)))
y_3_max = np.max(np.mean((y - y_0_mean)**4))
wo = int(self.seg_win_size_sec/self.seg_win_step_sec)
for i in np.arange(wo//2, y.shape[0] // seg_win_step - wo//2):
i_c = int((i+0.5)*seg_win_step)
y_slice = y_pad[i_c-seg_win_size//2:i_c+seg_win_size//2]
mean = np.mean(y_slice)
seg_y_data[i,0] = mean / y_0_max
seg_y_data[i,1] = np.mean((y_slice - mean)**2) / y_1_max
seg_y_data[i,2] = np.mean(np.abs((y_slice - mean)**3)) / y_2_max / 2
seg_y_data[i,3] = np.mean((y_slice - mean)**4) / y_3_max / 4
#
return seg_y_data