| |
| |
| """ |
| Beat and tempo |
| ============== |
| .. autosummary:: |
| :toctree: generated/ |
| |
| beat_track |
| plp |
| tempo |
| """ |
|
|
| import numpy as np |
| import scipy |
| import scipy.stats |
|
|
| from ._cache import cache |
| from . import core |
| from . import onset |
| from . import util |
| from .feature import tempogram, fourier_tempogram |
| from .util.exceptions import ParameterError |
| from .util.decorators import deprecate_positional_args |
|
|
| __all__ = ["beat_track", "tempo", "plp"] |
|
|
|
|
| @deprecate_positional_args |
| def beat_track( |
| *, |
| y=None, |
| sr=22050, |
| onset_envelope=None, |
| hop_length=512, |
| start_bpm=120.0, |
| tightness=100, |
| trim=True, |
| bpm=None, |
| prior=None, |
| units="frames", |
| ): |
| r"""Dynamic programming beat tracker. |
| |
| Beats are detected in three stages, following the method of [#]_: |
| |
| 1. Measure onset strength |
| 2. Estimate tempo from onset correlation |
| 3. Pick peaks in onset strength approximately consistent with estimated |
| tempo |
| |
| .. [#] Ellis, Daniel PW. "Beat tracking by dynamic programming." |
| Journal of New Music Research 36.1 (2007): 51-60. |
| http://labrosa.ee.columbia.edu/projects/beattrack/ |
| |
| Parameters |
| ---------- |
| y : np.ndarray [shape=(n,)] or None |
| audio time series |
| sr : number > 0 [scalar] |
| sampling rate of ``y`` |
| onset_envelope : np.ndarray [shape=(n,)] or None |
| (optional) pre-computed onset strength envelope. |
| hop_length : int > 0 [scalar] |
| number of audio samples between successive ``onset_envelope`` values |
| start_bpm : float > 0 [scalar] |
| initial guess for the tempo estimator (in beats per minute) |
| tightness : float [scalar] |
| tightness of beat distribution around tempo |
| trim : bool [scalar] |
| trim leading/trailing beats with weak onsets |
| bpm : float [scalar] |
| (optional) If provided, use ``bpm`` as the tempo instead of |
| estimating it from ``onsets``. |
| prior : scipy.stats.rv_continuous [optional] |
| An optional prior distribution over tempo. |
| If provided, ``start_bpm`` will be ignored. |
| units : {'frames', 'samples', 'time'} |
| The units to encode detected beat events in. |
| By default, 'frames' are used. |
| |
| Returns |
| ------- |
| tempo : float [scalar, non-negative] |
| estimated global tempo (in beats per minute) |
| beats : np.ndarray [shape=(m,)] |
| estimated beat event locations in the specified units |
| (default is frame indices) |
| .. note:: |
| If no onset strength could be detected, beat_tracker estimates 0 BPM |
| and returns an empty list. |
| |
| Raises |
| ------ |
| ParameterError |
| if neither ``y`` nor ``onset_envelope`` are provided, |
| or if ``units`` is not one of 'frames', 'samples', or 'time' |
| |
| See Also |
| -------- |
| librosa.onset.onset_strength |
| |
| Examples |
| -------- |
| Track beats using time series input |
| |
| >>> y, sr = librosa.load(librosa.ex('choice'), duration=10) |
| |
| >>> tempo, beats = librosa.beat.beat_track(y=y, sr=sr) |
| >>> tempo |
| 135.99917763157896 |
| |
| Print the frames corresponding to beats |
| |
| >>> beats |
| array([ 3, 21, 40, 59, 78, 96, 116, 135, 154, 173, 192, 211, |
| 230, 249, 268, 287, 306, 325, 344, 363]) |
| |
| Or print them as timestamps |
| |
| >>> librosa.frames_to_time(beats, sr=sr) |
| array([0.07 , 0.488, 0.929, 1.37 , 1.811, 2.229, 2.694, 3.135, |
| 3.576, 4.017, 4.458, 4.899, 5.341, 5.782, 6.223, 6.664, |
| 7.105, 7.546, 7.988, 8.429]) |
| |
| Track beats using a pre-computed onset envelope |
| |
| >>> onset_env = librosa.onset.onset_strength(y=y, sr=sr, |
| ... aggregate=np.median) |
| >>> tempo, beats = librosa.beat.beat_track(onset_envelope=onset_env, |
| ... sr=sr) |
| >>> tempo |
| 135.99917763157896 |
| >>> beats |
| array([ 3, 21, 40, 59, 78, 96, 116, 135, 154, 173, 192, 211, |
| 230, 249, 268, 287, 306, 325, 344, 363]) |
| |
| Plot the beat events against the onset strength envelope |
| |
| >>> import matplotlib.pyplot as plt |
| >>> hop_length = 512 |
| >>> fig, ax = plt.subplots(nrows=2, sharex=True) |
| >>> times = librosa.times_like(onset_env, sr=sr, hop_length=hop_length) |
| >>> M = librosa.feature.melspectrogram(y=y, sr=sr, hop_length=hop_length) |
| >>> librosa.display.specshow(librosa.power_to_db(M, ref=np.max), |
| ... y_axis='mel', x_axis='time', hop_length=hop_length, |
| ... ax=ax[0]) |
| >>> ax[0].label_outer() |
| >>> ax[0].set(title='Mel spectrogram') |
| >>> ax[1].plot(times, librosa.util.normalize(onset_env), |
| ... label='Onset strength') |
| >>> ax[1].vlines(times[beats], 0, 1, alpha=0.5, color='r', |
| ... linestyle='--', label='Beats') |
| >>> ax[1].legend() |
| """ |
|
|
| |
| if onset_envelope is None: |
| if y is None: |
| raise ParameterError("y or onset_envelope must be provided") |
|
|
| onset_envelope = onset.onset_strength( |
| y=y, sr=sr, hop_length=hop_length, aggregate=np.median |
| ) |
|
|
| |
| if not onset_envelope.any(): |
| return (0, np.array([], dtype=int)) |
|
|
| |
| if bpm is None: |
| bpm = tempo( |
| onset_envelope=onset_envelope, |
| sr=sr, |
| hop_length=hop_length, |
| start_bpm=start_bpm, |
| prior=prior, |
| )[0] |
|
|
| |
| beats = __beat_tracker(onset_envelope, bpm, float(sr) / hop_length, tightness, trim) |
|
|
| if units == "frames": |
| pass |
| elif units == "samples": |
| beats = core.frames_to_samples(beats, hop_length=hop_length) |
| elif units == "time": |
| beats = core.frames_to_time(beats, hop_length=hop_length, sr=sr) |
| else: |
| raise ParameterError("Invalid unit type: {}".format(units)) |
|
|
| return (bpm, beats) |
|
|
|
|
| @cache(level=30) |
| @deprecate_positional_args |
| def tempo( |
| *, |
| y=None, |
| sr=22050, |
| onset_envelope=None, |
| hop_length=512, |
| start_bpm=120, |
| std_bpm=1.0, |
| ac_size=8.0, |
| max_tempo=320.0, |
| aggregate=np.mean, |
| prior=None, |
| ): |
| """Estimate the tempo (beats per minute) |
| |
| Parameters |
| ---------- |
| y : np.ndarray [shape=(..., n)] or None |
| audio time series. Multi-channel is supported. |
| sr : number > 0 [scalar] |
| sampling rate of the time series |
| onset_envelope : np.ndarray [shape=(..., n)] |
| pre-computed onset strength envelope |
| hop_length : int > 0 [scalar] |
| hop length of the time series |
| start_bpm : float [scalar] |
| initial guess of the BPM |
| std_bpm : float > 0 [scalar] |
| standard deviation of tempo distribution |
| ac_size : float > 0 [scalar] |
| length (in seconds) of the auto-correlation window |
| max_tempo : float > 0 [scalar, optional] |
| If provided, only estimate tempo below this threshold |
| aggregate : callable [optional] |
| Aggregation function for estimating global tempo. |
| If `None`, then tempo is estimated independently for each frame. |
| prior : scipy.stats.rv_continuous [optional] |
| A prior distribution over tempo (in beats per minute). |
| By default, a pseudo-log-normal prior is used. |
| If given, ``start_bpm`` and ``std_bpm`` will be ignored. |
| |
| Returns |
| ------- |
| tempo : np.ndarray |
| estimated tempo (beats per minute). |
| If input is multi-channel, one tempo estimate per channel is provided. |
| |
| See Also |
| -------- |
| librosa.onset.onset_strength |
| librosa.feature.tempogram |
| |
| Notes |
| ----- |
| This function caches at level 30. |
| |
| Examples |
| -------- |
| >>> # Estimate a static tempo |
| >>> y, sr = librosa.load(librosa.ex('nutcracker'), duration=30) |
| >>> onset_env = librosa.onset.onset_strength(y=y, sr=sr) |
| >>> tempo = librosa.beat.tempo(onset_envelope=onset_env, sr=sr) |
| >>> tempo |
| array([143.555]) |
| |
| >>> # Or a static tempo with a uniform prior instead |
| >>> import scipy.stats |
| >>> prior = scipy.stats.uniform(30, 300) # uniform over 30-300 BPM |
| >>> utempo = librosa.beat.tempo(onset_envelope=onset_env, sr=sr, prior=prior) |
| >>> utempo |
| array([161.499]) |
| |
| >>> # Or a dynamic tempo |
| >>> dtempo = librosa.beat.tempo(onset_envelope=onset_env, sr=sr, |
| ... aggregate=None) |
| >>> dtempo |
| array([ 89.103, 89.103, 89.103, ..., 123.047, 123.047, 123.047]) |
| |
| >>> # Dynamic tempo with a proper log-normal prior |
| >>> prior_lognorm = scipy.stats.lognorm(loc=np.log(120), scale=120, s=1) |
| >>> dtempo_lognorm = librosa.beat.tempo(onset_envelope=onset_env, sr=sr, |
| ... aggregate=None, |
| ... prior=prior_lognorm) |
| >>> dtempo_lognorm |
| array([ 89.103, 89.103, 89.103, ..., 123.047, 123.047, 123.047]) |
| |
| Plot the estimated tempo against the onset autocorrelation |
| |
| >>> import matplotlib.pyplot as plt |
| >>> # Convert to scalar |
| >>> tempo = tempo.item() |
| >>> utempo = utempo.item() |
| >>> # Compute 2-second windowed autocorrelation |
| >>> hop_length = 512 |
| >>> ac = librosa.autocorrelate(onset_env, max_size=2 * sr // hop_length) |
| >>> freqs = librosa.tempo_frequencies(len(ac), sr=sr, |
| ... hop_length=hop_length) |
| >>> # Plot on a BPM axis. We skip the first (0-lag) bin. |
| >>> fig, ax = plt.subplots() |
| >>> ax.semilogx(freqs[1:], librosa.util.normalize(ac)[1:], |
| ... label='Onset autocorrelation', base=2) |
| >>> ax.axvline(tempo, 0, 1, alpha=0.75, linestyle='--', color='r', |
| ... label='Tempo (default prior): {:.2f} BPM'.format(tempo)) |
| >>> ax.axvline(utempo, 0, 1, alpha=0.75, linestyle=':', color='g', |
| ... label='Tempo (uniform prior): {:.2f} BPM'.format(utempo)) |
| >>> ax.set(xlabel='Tempo (BPM)', title='Static tempo estimation') |
| >>> ax.grid(True) |
| >>> ax.legend() |
| |
| Plot dynamic tempo estimates over a tempogram |
| |
| >>> fig, ax = plt.subplots() |
| >>> tg = librosa.feature.tempogram(onset_envelope=onset_env, sr=sr, |
| ... hop_length=hop_length) |
| >>> librosa.display.specshow(tg, x_axis='time', y_axis='tempo', cmap='magma', ax=ax) |
| >>> ax.plot(librosa.times_like(dtempo), dtempo, |
| ... color='c', linewidth=1.5, label='Tempo estimate (default prior)') |
| >>> ax.plot(librosa.times_like(dtempo_lognorm), dtempo_lognorm, |
| ... color='c', linewidth=1.5, linestyle='--', |
| ... label='Tempo estimate (lognorm prior)') |
| >>> ax.set(title='Dynamic tempo estimation') |
| >>> ax.legend() |
| """ |
|
|
| if start_bpm <= 0: |
| raise ParameterError("start_bpm must be strictly positive") |
|
|
| win_length = core.time_to_frames(ac_size, sr=sr, hop_length=hop_length).item() |
|
|
| tg = tempogram( |
| y=y, |
| sr=sr, |
| onset_envelope=onset_envelope, |
| hop_length=hop_length, |
| win_length=win_length, |
| ) |
|
|
| |
| if aggregate is not None: |
| tg = aggregate(tg, axis=-1, keepdims=True) |
|
|
| |
| bpms = core.tempo_frequencies(tg.shape[-2], hop_length=hop_length, sr=sr) |
|
|
| |
| if prior is None: |
| logprior = -0.5 * ((np.log2(bpms) - np.log2(start_bpm)) / std_bpm) ** 2 |
| else: |
| logprior = prior.logpdf(bpms) |
|
|
| |
| if max_tempo is not None: |
| max_idx = np.argmax(bpms < max_tempo) |
| logprior[:max_idx] = -np.inf |
| |
| logprior = util.expand_to(logprior, ndim=tg.ndim, axes=-2) |
|
|
| |
| |
| best_period = np.argmax(np.log1p(1e6 * tg) + logprior, axis=-2) |
|
|
| return np.take(bpms, best_period) |
|
|
|
|
| @deprecate_positional_args |
| def plp( |
| *, |
| y=None, |
| sr=22050, |
| onset_envelope=None, |
| hop_length=512, |
| win_length=384, |
| tempo_min=30, |
| tempo_max=300, |
| prior=None, |
| ): |
| """Predominant local pulse (PLP) estimation. [#]_ |
| |
| The PLP method analyzes the onset strength envelope in the frequency domain |
| to find a locally stable tempo for each frame. These local periodicities |
| are used to synthesize local half-waves, which are combined such that peaks |
| coincide with rhythmically salient frames (e.g. onset events on a musical time grid). |
| The local maxima of the pulse curve can be taken as estimated beat positions. |
| |
| This method may be preferred over the dynamic programming method of `beat_track` |
| when either the tempo is expected to vary significantly over time. Additionally, |
| since `plp` does not require the entire signal to make predictions, it may be |
| preferable when beat-tracking long recordings in a streaming setting. |
| |
| .. [#] Grosche, P., & Muller, M. (2011). |
| "Extracting predominant local pulse information from music recordings." |
| IEEE Transactions on Audio, Speech, and Language Processing, 19(6), 1688-1701. |
| |
| Parameters |
| ---------- |
| y : np.ndarray [shape=(..., n)] or None |
| audio time series. Multi-channel is supported. |
| |
| sr : number > 0 [scalar] |
| sampling rate of ``y`` |
| |
| onset_envelope : np.ndarray [shape=(..., n)] or None |
| (optional) pre-computed onset strength envelope |
| |
| hop_length : int > 0 [scalar] |
| number of audio samples between successive ``onset_envelope`` values |
| |
| win_length : int > 0 [scalar] |
| number of frames to use for tempogram analysis. |
| By default, 384 frames (at ``sr=22050`` and ``hop_length=512``) corresponds |
| to about 8.9 seconds. |
| |
| tempo_min, tempo_max : numbers > 0 [scalar], optional |
| Minimum and maximum permissible tempo values. ``tempo_max`` must be at least |
| ``tempo_min``. |
| |
| Set either (or both) to `None` to disable this constraint. |
| |
| prior : scipy.stats.rv_continuous [optional] |
| A prior distribution over tempo (in beats per minute). |
| By default, a uniform prior over ``[tempo_min, tempo_max]`` is used. |
| |
| Returns |
| ------- |
| pulse : np.ndarray, shape=[(..., n)] |
| The estimated pulse curve. Maxima correspond to rhythmically salient |
| points of time. |
| |
| If input is multi-channel, one pulse curve per channel is computed. |
| |
| See Also |
| -------- |
| beat_track |
| librosa.onset.onset_strength |
| librosa.feature.fourier_tempogram |
| |
| Examples |
| -------- |
| Visualize the PLP compared to an onset strength envelope. |
| Both are normalized here to make comparison easier. |
| |
| >>> y, sr = librosa.load(librosa.ex('brahms')) |
| >>> onset_env = librosa.onset.onset_strength(y=y, sr=sr) |
| >>> pulse = librosa.beat.plp(onset_envelope=onset_env, sr=sr) |
| >>> # Or compute pulse with an alternate prior, like log-normal |
| >>> import scipy.stats |
| >>> prior = scipy.stats.lognorm(loc=np.log(120), scale=120, s=1) |
| >>> pulse_lognorm = librosa.beat.plp(onset_envelope=onset_env, sr=sr, |
| ... prior=prior) |
| >>> melspec = librosa.feature.melspectrogram(y=y, sr=sr) |
| |
| >>> import matplotlib.pyplot as plt |
| >>> fig, ax = plt.subplots(nrows=3, sharex=True) |
| >>> librosa.display.specshow(librosa.power_to_db(melspec, |
| ... ref=np.max), |
| ... x_axis='time', y_axis='mel', ax=ax[0]) |
| >>> ax[0].set(title='Mel spectrogram') |
| >>> ax[0].label_outer() |
| >>> ax[1].plot(librosa.times_like(onset_env), |
| ... librosa.util.normalize(onset_env), |
| ... label='Onset strength') |
| >>> ax[1].plot(librosa.times_like(pulse), |
| ... librosa.util.normalize(pulse), |
| ... label='Predominant local pulse (PLP)') |
| >>> ax[1].set(title='Uniform tempo prior [30, 300]') |
| >>> ax[1].label_outer() |
| >>> ax[2].plot(librosa.times_like(onset_env), |
| ... librosa.util.normalize(onset_env), |
| ... label='Onset strength') |
| >>> ax[2].plot(librosa.times_like(pulse_lognorm), |
| ... librosa.util.normalize(pulse_lognorm), |
| ... label='Predominant local pulse (PLP)') |
| >>> ax[2].set(title='Log-normal tempo prior, mean=120', xlim=[5, 20]) |
| >>> ax[2].legend() |
| |
| PLP local maxima can be used as estimates of beat positions. |
| |
| >>> tempo, beats = librosa.beat.beat_track(onset_envelope=onset_env) |
| >>> beats_plp = np.flatnonzero(librosa.util.localmax(pulse)) |
| >>> import matplotlib.pyplot as plt |
| >>> fig, ax = plt.subplots(nrows=2, sharex=True, sharey=True) |
| >>> times = librosa.times_like(onset_env, sr=sr) |
| >>> ax[0].plot(times, librosa.util.normalize(onset_env), |
| ... label='Onset strength') |
| >>> ax[0].vlines(times[beats], 0, 1, alpha=0.5, color='r', |
| ... linestyle='--', label='Beats') |
| >>> ax[0].legend() |
| >>> ax[0].set(title='librosa.beat.beat_track') |
| >>> ax[0].label_outer() |
| >>> # Limit the plot to a 15-second window |
| >>> times = librosa.times_like(pulse, sr=sr) |
| >>> ax[1].plot(times, librosa.util.normalize(pulse), |
| ... label='PLP') |
| >>> ax[1].vlines(times[beats_plp], 0, 1, alpha=0.5, color='r', |
| ... linestyle='--', label='PLP Beats') |
| >>> ax[1].legend() |
| >>> ax[1].set(title='librosa.beat.plp', xlim=[5, 20]) |
| >>> ax[1].xaxis.set_major_formatter(librosa.display.TimeFormatter()) |
| |
| """ |
|
|
| |
| if onset_envelope is None: |
| onset_envelope = onset.onset_strength( |
| y=y, sr=sr, hop_length=hop_length, aggregate=np.median |
| ) |
|
|
| if tempo_min is not None and tempo_max is not None and tempo_max <= tempo_min: |
| raise ParameterError( |
| "tempo_max={} must be larger than tempo_min={}".format(tempo_max, tempo_min) |
| ) |
|
|
| |
| ftgram = fourier_tempogram( |
| onset_envelope=onset_envelope, |
| sr=sr, |
| hop_length=hop_length, |
| win_length=win_length, |
| ) |
|
|
| |
| tempo_frequencies = core.fourier_tempo_frequencies( |
| sr=sr, hop_length=hop_length, win_length=win_length |
| ) |
|
|
| if tempo_min is not None: |
| ftgram[..., tempo_frequencies < tempo_min, :] = 0 |
| if tempo_max is not None: |
| ftgram[..., tempo_frequencies > tempo_max, :] = 0 |
|
|
| |
| tempo_frequencies = util.expand_to(tempo_frequencies, ndim=ftgram.ndim, axes=-2) |
|
|
| |
| ftmag = np.log1p(1e6 * np.abs(ftgram)) |
| if prior is not None: |
| ftmag += prior.logpdf(tempo_frequencies) |
|
|
| peak_values = ftmag.max(axis=-2, keepdims=True) |
| ftgram[ftmag < peak_values] = 0 |
|
|
| |
| ftgram /= util.tiny(ftgram) ** 0.5 + np.abs(ftgram.max(axis=-2, keepdims=True)) |
|
|
| |
| pulse = core.istft( |
| ftgram, hop_length=1, n_fft=win_length, length=onset_envelope.shape[-1] |
| ) |
|
|
| |
| pulse = np.clip(pulse, 0, None, pulse) |
|
|
| |
| return util.normalize(pulse, axis=-1) |
|
|
|
|
| def __beat_tracker(onset_envelope, bpm, fft_res, tightness, trim): |
| """Internal function that tracks beats in an onset strength envelope. |
| |
| Parameters |
| ---------- |
| onset_envelope : np.ndarray [shape=(n,)] |
| onset strength envelope |
| bpm : float [scalar] |
| tempo estimate |
| fft_res : float [scalar] |
| resolution of the fft (sr / hop_length) |
| tightness : float [scalar] |
| how closely do we adhere to bpm? |
| trim : bool [scalar] |
| trim leading/trailing beats with weak onsets? |
| |
| Returns |
| ------- |
| beats : np.ndarray [shape=(n,)] |
| frame numbers of beat events |
| """ |
|
|
| if bpm <= 0: |
| raise ParameterError("bpm must be strictly positive") |
|
|
| |
| period = round(60.0 * fft_res / bpm) |
|
|
| |
| localscore = __beat_local_score(onset_envelope, period) |
|
|
| |
| backlink, cumscore = __beat_track_dp(localscore, period, tightness) |
|
|
| |
| beats = [__last_beat(cumscore)] |
|
|
| |
| while backlink[beats[-1]] >= 0: |
| beats.append(backlink[beats[-1]]) |
|
|
| |
| |
| beats = np.array(beats[::-1], dtype=int) |
|
|
| |
| beats = __trim_beats(localscore, beats, trim) |
|
|
| return beats |
|
|
|
|
| |
| def __normalize_onsets(onsets): |
| """Maps onset strength function into the range [0, 1]""" |
|
|
| norm = onsets.std(ddof=1) |
| if norm > 0: |
| onsets = onsets / norm |
| return onsets |
|
|
|
|
| def __beat_local_score(onset_envelope, period): |
| """Construct the local score for an onset envlope and given period""" |
|
|
| window = np.exp(-0.5 * (np.arange(-period, period + 1) * 32.0 / period) ** 2) |
| return scipy.signal.convolve(__normalize_onsets(onset_envelope), window, "same") |
|
|
|
|
| def __beat_track_dp(localscore, period, tightness): |
| """Core dynamic program for beat tracking""" |
|
|
| backlink = np.zeros_like(localscore, dtype=int) |
| cumscore = np.zeros_like(localscore) |
|
|
| |
| window = np.arange(-2 * period, -np.round(period / 2) + 1, dtype=int) |
|
|
| |
| if tightness <= 0: |
| raise ParameterError("tightness must be strictly positive") |
|
|
| txwt = -tightness * (np.log(-window / period) ** 2) |
|
|
| |
| first_beat = True |
| for i, score_i in enumerate(localscore): |
|
|
| |
| z_pad = np.maximum(0, min(-window[0], len(window))) |
|
|
| |
| candidates = txwt.copy() |
| candidates[z_pad:] = candidates[z_pad:] + cumscore[window[z_pad:]] |
|
|
| |
| beat_location = np.argmax(candidates) |
|
|
| |
| cumscore[i] = score_i + candidates[beat_location] |
|
|
| |
| if first_beat and score_i < 0.01 * localscore.max(): |
| backlink[i] = -1 |
| else: |
| backlink[i] = window[beat_location] |
| first_beat = False |
|
|
| |
| window = window + 1 |
|
|
| return backlink, cumscore |
|
|
|
|
| def __last_beat(cumscore): |
| """Get the last beat from the cumulative score array""" |
|
|
| maxes = util.localmax(cumscore) |
| med_score = np.median(cumscore[np.argwhere(maxes)]) |
|
|
| |
| return np.argwhere((cumscore * maxes * 2 > med_score)).max() |
|
|
|
|
| def __trim_beats(localscore, beats, trim): |
| """Final post-processing: throw out spurious leading/trailing beats""" |
|
|
| smooth_boe = scipy.signal.convolve(localscore[beats], scipy.signal.hann(5), "same") |
|
|
| if trim: |
| threshold = 0.5 * ((smooth_boe ** 2).mean() ** 0.5) |
| else: |
| threshold = 0.0 |
|
|
| valid = np.argwhere(smooth_boe > threshold) |
|
|
| return beats[valid.min() : valid.max()] |
|
|