diff --git a/beat.py b/beat.py index 7e1d73a..1af0b20 100644 --- a/beat.py +++ b/beat.py @@ -169,11 +169,12 @@ class RegularBeatFinder: def find_beat(self, fs, ssf_zxings, f_hint=None, debug_fe=False, debug_i=None): """Find the optimal beat frequency.""" act_ibis = np.diff(ssf_zxings) + # nice-to: may be interesting to also use as score: the ssf amplitude info at the beats to which we aligned # evaluate mean absolute errors for all frequencies freqs, freq_errs = self._get_opt_ibi_freq_2(fs, act_ibis, debug_i) # bias with f_hint - once we know the beat freq, make it more likely for it to be found everywhere if f_hint is not None: - nf, f1, f2 = RegularBeatFinder.num_freqs, RegularBeatFinder.range_f2, RegularBeatFinder.range_f1 + nf, f1, f2 = RegularBeatFinder.num_freqs, RegularBeatFinder.range_f1, RegularBeatFinder.range_f2 bias = gauss( nf, (f_hint - f1) / (f2 - f1) * nf, diff --git a/song.py b/song.py index 017ad78..3b22e36 100644 --- a/song.py +++ b/song.py @@ -8,8 +8,9 @@ from sqi import gauss, shift class SongBeatDetector: SEGMENT_SLICE_LEN_SEC = 8.0 #: slice length for processing (long enough to contain bar structure; short enough for a constant freq. beat placement) SSF_REL_THRES = 1.5 #: optimize for slope of error (mae) function over beat frequency + NE_THRES = 30.0 #: normalized error threshold for 'good' slices def __init__(self): pass - def detect(self, fs, sig, debug_fe_idx=None): + def detect(self, fs, sig, use_f_hint=True, debug_fe_idx=None): self.fs = fs #self.sig = sig @@ -31,14 +32,27 @@ class SongBeatDetector: # we segment on 'guitar' info, but process 'bass' later - seg_sl = int(SongBeatDetector.SEGMENT_SLICE_LEN_SEC * fsd) + if use_f_hint: + # initial estimate (without 'f_hint') + zds_initial = self._estimate_segments(debug_fe_idx=None) + self.zds_initial = zds_initial + ifbs_good = np.array([zdd['ne'] < SongBeatDetector.NE_THRES for zdd in zds_initial]) + fbs = np.array([zdd['fb'] for zdd in zds_initial])[np.where(ifbs_good)[0]] + bins, hfreq = np.histogram(fbs) + ih = np.argmax(bins) + self.f_hint = np.mean((hfreq[ih], hfreq[ih+1])) # center freq of bin + else: + self.f_hint = None + + # actual estimate (using 'f_hint' to bias each segment) + self.zds = self._estimate_segments(f_hint=self.f_hint, debug_fe_idx=debug_fe_idx) - self.zds = self._estimate_segments(debug_fe_idx=debug_fe_idx) return self.zds def _estimate_segments(self, f_hint=None, debug_fe_idx=None): zds = [] fsd = self.fsd + seg_sl = int(SongBeatDetector.SEGMENT_SLICE_LEN_SEC * fsd) # segment slice length in 1/fsd units # for each segment for i in np.arange(self.i_seg.shape[0]-1): i1, i2 = self.i_seg[i], self.i_seg[i+1] @@ -56,17 +70,16 @@ class SongBeatDetector: debug_fe = i1 <= debug_fe_sidx < i2 else: debug_fe = False - zdd = self._process_slice(j1, j2, m, seg_sl, sig_slice, f_hint=f_hint, debug_fe=debug_fe) + zdd = self._process_slice(j1, j2, m, sig_slice, f_hint=f_hint, debug_fe=debug_fe) zds.append(zdd) return zds - def _process_slice(self, j1, j2, m, seg_sl, sig_slice, f_hint=None, debug_fe=False): + def _process_slice(self, j1, j2, m, sig_slice, f_hint=None, debug_fe=False): """ :param j1: lower index into 'sig_slice' :param j2: upper index into 'sig_slice' :param m: slice number (used to check if debugging) - :param seg_sl: segment slice length in 1/fsd units :param debug_fe: show plots for SSF and raw/reg beat placement """ # TODO: C++ impl of SsfZxing._ssf_det_zxings() has diverged. @@ -75,7 +88,8 @@ class SongBeatDetector: # - ?? others ?? # NOTE: SsfZxing here is always getting short 8-sec slices only (nb. for 'ssf_th' comput.) - fsd = self.fsd #: reciprocal window step size + fsd = self.fsd # reciprocal window step size + seg_sl = int(SongBeatDetector.SEGMENT_SLICE_LEN_SEC * fsd) # segment slice length in 1/fsd units SsfZxing.ssf_rel_thres = SongBeatDetector.SSF_REL_THRES zd = SsfZxing()