Skip to content

10 ‐ Pulse, Cycles and Alignment

Alexander Refsum Jensenius edited this page Jul 17, 2026 · 1 revision

Plain numpy/scipy functions for pulse-level analysis of any 1-D signal or event-time array (onset-detection functions, quantity-of-motion curves, accelerometer speed, audio envelopes, ...). No MgVideo/MgAudio object is required. This page covers two closely related toolkits: peak-picking and cycle analysis (segmenting a stream of onsets into rhythmic cycles) and audio–motion alignment (lead/lag and event-matching between two independently-clocked streams).


Peak picking and cycle analysis (_peaks / _pulse)

pick_peaks() is the toolbox's one canonical peak-picker, shared by the pulse, alignment, quantity-of-motion and audio-feature modules. _pulse builds on it to segment a stream of stroke onsets into rhythmic cycles and fit an accelerando model — developed for the ro ritual's accelerating double-drum-stroke pattern, but applicable to any cyclic, accelerating onset sequence.

Available functions

  • pick_peaks(x, fs=1.0, smooth=3, rel_threshold=0.5, min_interval=0.3, rel_prominence=0.2, threshold=None, prominence=None) — adaptive peak-picker: optional moving-average smoothing, a relative/absolute amplitude threshold, a minimum inter-peak interval, and an optional prominence gate. Returns integer sample indices.
  • group_strokes(onset_times, max_strokes=4, ...) — segment stroke onsets into per-cycle stroke groups by dynamic-programming over candidate segmentations (size, within-gap, ordering and accelerando priors). Returns a list of onset-time groups.
  • segment_cycles(onset_times, event_times=None, **kwargs) — like group_strokes() but returns Cycle objects, each optionally carrying a secondary event (e.g. a shout) that falls inside the cycle.
  • Cycle — dataclass for one rhythmic cycle (index, strokes, event); properties t_start, n_strokes, stroke_gap.
  • cycle_table(cycles, clip_id="", context="") — tabulate per-cycle metrics (t, ioi, n_strokes, stroke_gap, event, stroke_event_ioi) into a pandas.DataFrame.
  • fit_accelerando(times, iois) — least-squares fit of IOI(t) = ioi0 * 2**(-t / t_double); returns (ioi0, t_double, r2).
  • motion_onsets(motion, fs, min_interval=0.25, smooth_cutoff=8.0) — onset times of the steepest sustained rises in a motion signal (low-pass, differentiate, peak-pick).

Quick example

import musicalgestures as mg
import numpy as np

# Stroke onsets from an accelerating double-stroke pattern (e.g. drum strokes)
onset_times = np.array([0.0, 0.30, 2.0, 2.28, 3.7, 3.95, 5.0, 5.20])

cycles = mg.segment_cycles(onset_times)
table = mg.cycle_table(cycles, clip_id="take01", context="ro")
ioi0, t_double, r2 = mg.fit_accelerando(table["t"].to_numpy(), table["ioi"].to_numpy())

# Peak-pick a quantity-of-motion curve
idx = mg.pick_peaks(qom_curve, fs=30.0, rel_threshold=0.4, min_interval=0.2)
onset_times_motion = mg.motion_onsets(qom_curve, fs=30.0)

Audio–motion alignment (_alignment)

Lead/lag estimation and event alignment between two independently-clocked streams (e.g. audio onsets vs. motion onsets, or two camera angles). Pass in numpy arrays of envelopes or event times.

Available functions

  • xcorr_lag(x, y, fs, max_lag=1.5) — canonical lead/lag estimate between two signals by cross-correlation, searched within ±max_lag seconds; returns (lag, r), positive lag = y happens after x.
  • envelope_lag(x, y, rate, max_lag_s=1.5) — thin wrapper around xcorr_lag(), kept as the ro study's interface for envelope-to-envelope lags (e.g. voice vs. motion envelope).
  • per_cycle_motion_delta(cycle_starts, motion_times, lookback=0.3) — for each rhythmic cycle (see Peak picking and cycle analysis above), the time of its assigned motion onset minus the cycle start.
  • anchor_and_match(times_a, times_b, anchor_a=None, anchor_b=None, weights_a=None, weights_b=None, window=0.15) — per-take relative event alignment between two streams with independent clocks: both streams are anchored at their strongest event, then non-anchor events are matched one-to-one within ±window seconds. Returns signed offsets (b minus a).
  • offset_stats(offsets) — summary statistics (n, median, mean, std, iqr, abs_median, min, max) of a signed-offset distribution, e.g. from anchor_and_match().
  • sliding_correlation(x, y, fs, window=10.0, step=2.0, min_std=1e-6) — local (windowed) Pearson correlation profile between two signals, for testing whether globally-uncorrelated streams couple locally.
  • envelope_agreement(signals, fs, smooth=1.0) — agreement (correlation matrix + mean off-diagonal) among N parallel envelopes, e.g. per-camera-angle motion curves of the same performance.

Quick example

import musicalgestures as mg

# Lead/lag between an audio onset envelope and a motion envelope
lag, r = mg.xcorr_lag(audio_envelope, motion_envelope, fs=100.0, max_lag=1.5)

# Relative timing agreement between kinematic impacts and audio onsets
offsets = mg.anchor_and_match(impact_times, audio_onset_times, window=0.15)
stats = mg.offset_stats(offsets)

# Cross-view agreement of five camera angles' motion envelopes
C, mean_r = mg.envelope_agreement([env_cam1, env_cam2, env_cam3, env_cam4, env_cam5], fs=25.0)

Provenance

pick_peaks() reimplements the cymbal-comparison study's (Jensenius) peak-picking convention and also subsumes the peak-picking conventions used in the Westney-comparisons and ro studies; its defaults are PROVISIONAL (the source study's own reported values vary by signal type — see the function docstring). The rest of _pulse — stroke grouping, cycle tables, the accelerando fit and motion onsets — implements the ro study's (Jensenius) analysis of the accelerating ro ritual's drum-stroke cycles and their coupling to body motion.

_alignment unifies the alignment methods of three studies (Jensenius): the ro study's envelope-lag and per-cycle motion-onset delta (rowing-gesture onsets vs. drum cycles); the cymbal-comparison study's anchor-and-match relative event alignment and offset statistics (audio-onset vs. kinematic-impact timing agreement without a common clock); and the Westney-comparisons study's cross-correlation sync, sliding-window coupling, and N-source envelope agreement (cross-view motion-envelope agreement, local motion-loudness coupling).

API docs

Clone this wiki locally