Pulse¶
Pulse and cycle segmentation for accelerating rhythmic sequences.
Tools for grouping event onsets (e.g. drum strokes) into per-cycle stroke groups, tabulating per-cycle metrics, fitting an exponential accelerando model, and detecting motion onsets from a quantity-of-motion signal.
These helpers are independent of the MgVideo/MgAudio classes and operate on plain numpy arrays of onset times or 1-D motion signals.
Source: ro study (Jensenius) -- analysis of the accelerating ro ritual's drum-stroke cycles and their coupling to body motion.
Cycle
dataclass
¶
Cycle(index, strokes, event=None)
One rhythmic cycle: a group of stroke onsets plus an optional secondary event (e.g. a shout) that falls inside the cycle.
Source: ro study (Jensenius).
Attributes:
| Name | Type | Description |
|---|---|---|
index |
int
|
Zero-based cycle index. |
strokes |
list
|
Onset times (s) of the strokes in this cycle. |
event |
float | None
|
Time (s) of the cycle's secondary event, or None. |
group_strokes ¶
group_strokes(onset_times, max_strokes=4, gap_lo=0.1, gap_hi=0.6, w_abs=6.0, w_within=4.0, tol_within=0.15, w_order=2.5, w_trend=2.0, tol_trend=0.25, size_costs=(1.0, 0.0, 1.5, 6.0))
Segment stroke onsets into per-cycle stroke groups by dynamic programming over candidate group boundaries (Viterbi over segmentations), with a greedily carried stroke-gap estimate (EMA) that is exact given that carried estimate but is not itself part of the DP state key, so Bellman optimality does not strictly hold over the full segmentation.
The cost of a segmentation encodes structural priors for an accelerating cyclic pattern (developed for the ro ritual's double drum strokes):
- size prior --
size_costs[k-1]per k-stroke group: with the defaults, double strokes are free, singles/triples carry a small penalty, >=4 a steep one; - within-gap plausibility -- gaps inside a group should fall in
[gap_lo, gap_hi]seconds (w_absx log-excess outside) and stay close to a running stroke-gap estimate (EMA, weight 0.5):w_withinx |log-ratio| beyondtol_within; - ordering prior -- a between-group gap shorter than the current
stroke-gap estimate costs
w_orderx the log-ratio shortfall; - accelerando prior -- successive between-group gaps should not grow:
an increase beyond
tol_trend(log) costsw_trendx the excess (decreases are free, so a climax's shrinking gaps cost nothing).
Unlike a single running threshold, the trend and ordering terms let the decision boundary between stroke gaps and cycle gaps shrink with the accelerando, so the climax stays resolved even when the cycle gap drops to (or just below) the stroke gap in the final cycles.
Source: ro study (Jensenius).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
onset_times
|
ndarray
|
Stroke onset times in seconds (any order). |
required |
max_strokes
|
int
|
Maximum strokes per group. Defaults to 4. |
4
|
gap_lo
|
float
|
Lower bound (s) of plausible within-group gaps. Defaults to 0.10. |
0.1
|
gap_hi
|
float
|
Upper bound (s) of plausible within-group gaps. Defaults to 0.60. |
0.6
|
w_abs
|
float
|
Weight of the absolute within-gap plausibility term. Defaults to 6.0. |
6.0
|
w_within
|
float
|
Weight of the within-gap-vs-EMA term. Defaults to 4.0. |
4.0
|
tol_within
|
float
|
Log-ratio tolerance of the within-gap-vs-EMA term. Defaults to 0.15. |
0.15
|
w_order
|
float
|
Weight of the ordering prior. Defaults to 2.5. |
2.5
|
w_trend
|
float
|
Weight of the accelerando (non-increasing gaps) prior. Defaults to 2.0. |
2.0
|
tol_trend
|
float
|
Log-ratio tolerance of the accelerando prior. Defaults to 0.25. |
0.25
|
size_costs
|
tuple
|
Cost per group of size 1, 2, 3, >=4. Defaults to (1.0, 0.0, 1.5, 6.0). |
(1.0, 0.0, 1.5, 6.0)
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
A list of groups, each a list of stroke onset times (floats, seconds). |
Source code in musicalgestures/_pulse.py
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | |
segment_cycles ¶
segment_cycles(onset_times, event_times=None, **kwargs)
Segment stroke onsets into Cycle objects: one Cycle per stroke group
(see group_strokes); the cycle's secondary event is the first event
onset in [group start, next group start).
Source: ro study (Jensenius) -- the secondary events were the ritual's shouts.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
onset_times
|
ndarray
|
Stroke onset times in seconds. |
required |
event_times
|
ndarray
|
Onset times (s) of a secondary event stream (e.g. shouts) to assign to cycles. Defaults to None. |
None
|
**kwargs
|
Passed on to |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
list |
A list of |
Source code in musicalgestures/_pulse.py
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | |
cycle_table ¶
cycle_table(cycles, clip_id='', context='')
Tabulate per-cycle metrics from a list of Cycle objects.
Source: ro study (Jensenius).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cycles
|
list
|
A list of |
required |
clip_id
|
str
|
Identifier written to the |
''
|
context
|
str
|
Label written to the |
''
|
Returns:
| Type | Description |
|---|---|
|
pd.DataFrame: One row per cycle with columns |
Source code in musicalgestures/_pulse.py
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
fit_accelerando ¶
fit_accelerando(times, iois)
Fit an exponential accelerando model IOI(t) = ioi0 * 2**(-t / t_double)
via least squares on log2(IOI): t_double is the time (s) it takes the
inter-onset interval to halve (i.e. the tempo to double).
Source: ro study (Jensenius).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
times
|
ndarray
|
Cycle start times in seconds. |
required |
iois
|
ndarray
|
Inter-onset intervals (s) at those times. Non-finite or non-positive entries are ignored. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
|
Source code in musicalgestures/_pulse.py
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 | |
motion_onsets ¶
motion_onsets(motion, fs, min_interval=0.25, smooth_cutoff=8.0)
Times of the steepest sustained rises in a motion signal (e.g. a
quantity-of-motion curve): the signal is low-pass filtered, its positive
time-derivative is formed, and peaks of that derivative are picked with
the canonical peak-picker (musicalgestures.pick_peaks) using a robust
prominence gate of 0.25 x (99th percentile - median) of the derivative.
Source: ro study (Jensenius) -- motion onsets of the rowing gesture,
related to the drum cycles via per_cycle_motion_delta.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
motion
|
ndarray
|
1-D motion signal (e.g. mean absolute frame difference). |
required |
fs
|
float
|
Sampling rate of the signal (Hz, e.g. video frames per second). |
required |
min_interval
|
float
|
Minimum interval between onsets (s). Defaults to 0.25. |
0.25
|
smooth_cutoff
|
float
|
Low-pass cutoff (Hz) applied before differentiation. Defaults to 8.0. |
8.0
|
Returns:
| Type | Description |
|---|---|
|
np.ndarray: Onset times in seconds. |
Source code in musicalgestures/_pulse.py
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 | |