motion.lib
Motion library. Its official prefix is mo.
This library provides helpers for motion and orientation processing: shock detection, inclination and gravity projection, acceleration and gyroscope envelopes, orientation weighting toward the device axes, and utilities for normalized motion streams.
Usage:
mo = library("motion.lib");
process = mo.shockTrigger(50, 0.75, 75, accX);
All motion axes are expected to be normalized to [-1, 1] (e.g., gravity ~= 1 g). Functions return signals in [0, 1] unless documented otherwise. Time parameters are in milliseconds unless noted. Some helpers (e.g., projectedGravity) allow an optional dead-zone offset: low magnitudes are zeroed, and the remaining span is rescaled to preserve the 0..1 range.
Accelerometer axes carry gravity: a device lying still already has a total
magnitude of about 1 g, so the acceleration envelopes saturate on a motionless
device unless the static component is removed first. Feed them axes that have
been high-passed with fi.dcblockerat when what you want is movement rather
than orientation. The inclination helpers want the opposite: raw axes, gravity
included.
Typical use-cases:
- Map shocks to drum triggers or one-shot events.
- Derive smooth inclination or projected-gravity controls for fades/pans.
- Track acceleration/gyro envelopes to drive dynamics (filters, gains, FX sends).
- Weight device orientation toward axes for spatial routing or mode selection.
- Normalize motion streams with dead-zones to reduce jitter or false positives.
The Motion library is organized into 7 sections:
- Shock Detection
- Inclination and Gravity Projection
- Envelopes Helpers
- Acceleration Envelopes
- Gyroscope Envelopes
- Orientation Weighting
- Utility Scaling
References
Shock Detection
(mo.)shockTrigger
Debounced shock trigger from an accelerometer axis.
Usage
shockTrigger(hpHz, threshold, debounceMs, sig) : _
Where:
hpHz: high-pass frequency (Hz) used to isolate shocks ( > 0 )threshold: trigger threshold applied after high-pass (normalized g units)debounceMs: time in milliseconds to hold the trigger high (>= 0)sig: accelerometer axis signal (normalized, typically [-1, 1])
Output is a gate in [0, 1].
Example
mo = library("motion.lib");
process = mo.shockTrigger(50, 0.75, 75, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
shockTrigger_test = mo.shockTrigger(50, 0.5, 50, os.pulsetrain(2, 0.5));
Inclination and Gravity Projection
(mo.)inclinometer
Low-pass inclinometer for a single axis.
Usage
inclinometer(lpHz, sig) : _
Where:
lpHz: low-pass frequency (Hz) ( > 0 )sig: accelerometer axis signal (normalized, typically [-1, 1])
Output is clamped to [0, 1] with negative values removed.
Example
mo = library("motion.lib");
process = mo.inclinometer(1.5, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
inclinometer_test = mo.inclinometer(2, os.sawtooth(1));
(mo.)inclineBalance
Balance between positive and negative inclination on the same axis.
Usage
inclineBalance(lpHz, posSig, negSig) : _
Where:
lpHz: low-pass frequency (Hz) ( > 0 )posSig: positive-facing accelerometer axis signal (normalized, typically [-1, 1])negSig: negative-facing accelerometer axis signal (normalized, typically [-1, 1])
Output maps posSig -> 1 and negSig -> 0 with smoothing and clamping.
Example
mo = library("motion.lib");
process = mo.inclineBalance(1.5, accPosX, accNegX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
inclineBalance_test =
mo.inclineBalance(1.5, os.sawtooth(0.2) * 0.5 + 0.5, os.sawtooth(0.2) * (-0.5));
(mo.)inclineSymmetric
Symmetric gravity comparison (0->1->0) from positive and negative axes.
Usage
inclineSymmetric(lpHz, posSig, negSig) : _
Where:
lpHz: low-pass frequency (Hz) ( > 0 )posSig: positive-facing accelerometer axis signal (normalized, typically [-1, 1])negSig: negative-facing accelerometer axis signal (normalized, typically [-1, 1])
Output peaks at 1 near either pole and returns to 0 near the midpoint.
Example
mo = library("motion.lib");
process = mo.inclineSymmetric(1.5, accPosX, accNegX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
inclineSymmetric_test =
mo.inclineSymmetric(1.5, os.triangle(0.3) * 0.5 + 0.5, os.triangle(0.3) * (-0.5));
(mo.)projectedGravity
Projects an axis onto gravity with optional dead-zone offset.
Usage
projectedGravity(lpHz, offset, sig) : _
Where:
lpHz: low-pass frequency (Hz) ( > 0 )offset: dead-zone offset applied after projection (0..0.33). Magnitudes belowoffsetclamp to 0, and the remaining range is rescaled to keep the output in [0, 1].sig: accelerometer axis signal (normalized, typically [-1, 1])
Output is normalized to [0, 1] with an optional dead zone near 0.
Example
mo = library("motion.lib");
process = mo.projectedGravity(1.5, 0.08, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
projectedGravity_test = mo.projectedGravity(2, 0.05, os.triangle(0.1));
Envelopes Helpers
(mo.)motionEnvelope
Base thresholded AR envelope used by the accelerometer and gyroscope helpers.
Usage
motionEnvelope(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: input signal (normalized)
Signal is offset by thr, floored at 0, scaled by gain, clamped to [0, 1], then
fed to an attack/release follower (an.amp_follower_ar).
Example
mo = library("motion.lib");
process = mo.motionEnvelope(0.05, 1.25, 15, 25, accX);
(mo.)motionEnvelopeRange
Thresholded AR envelope parameterized by an input range instead of a gain.
Usage
motionEnvelopeRange(lo, hi, envUpMs, envDownMs, sig) : _
Where:
lo: input value at which the output leaves 0 (0..1)hi: input value at which the output reaches 1 (must be >lo)envUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: input signal (normalized)
Same detector as motionEnvelope, but the dead-zone is given by its two
endpoints rather than by a threshold plus a matching gain. motionEnvelope
requires the caller to derive gain = 1 / (hi - thr) by hand to reach full
scale at hi; getting that wrong silently rescales the whole control. Prefer
this variant whenever the useful input span is known.
Example
mo = library("motion.lib");
// full scale at 0.6 g of movement, nothing below 0.15 g
process = mo.motionEnvelopeRange(0.15, 0.6, 15, 25, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
motionEnvelopeRange_test =
mo.motionEnvelopeRange(0.2, 0.5, 15, 25, os.sawtooth(0.5) * 0.5 + 0.5);
(mo.)motionEnvelopeUD
Thresholded envelope with a peak detector and a symmetric smoother.
Usage
motionEnvelopeUD(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: smoothing time constant in milliseconds, applied in both directions (>= 0)envDownMs: peak-decay time constant in milliseconds (>= 0)sig: input signal (normalized)
Same conditioning as motionEnvelope but fed to an.amp_follower_ud instead
of an.amp_follower_ar: a peak follower with a envDownMs decay, cascaded
with a symmetric one-pole of time constant envUpMs. Two poles instead of
one, so the decay is rounder and never discontinuous, which suits slow hand
gestures better than the switching follower. Note that the two parameters are
not attack and release here: envUpMs smooths both directions, and
envDownMs alone at 0 still leaves the envUpMs smoothing in place.
Example
mo = library("motion.lib");
process = mo.motionEnvelopeUD(0.05, 1.25, 670, 0, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
motionEnvelopeUD_test =
mo.motionEnvelopeUD(0.05, 1.25, 120, 40, os.triangle(0.4) * 0.5 + 0.5);
(mo.)envelopeAbs
Envelope on the absolute value of a signal (responds to both polarities).
Usage
envelopeAbs(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr,gain,envUpMs,envDownMs: passed tomotionEnvelopesig: input signal
Absolute value is taken before envelope detection.
(mo.)envelopePos
Envelope for the positive portion of a signal.
Usage
envelopePos(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr,gain,envUpMs,envDownMs: passed tomotionEnvelopesig: input signal
Negative values are ignored by the thresholding stage.
(mo.)envelopeNeg
Envelope for the negative portion of a signal (by flipping its polarity first).
Usage
envelopeNeg(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr,gain,envUpMs,envDownMs: passed tomotionEnvelopesig: input signal
The signal is negated before the shared positive-only detector.
(mo.)pita3
3D magnitude helper sqrt(x^2 + y^2 + z^2) used for total envelopes.
Usage
pita3(x, y, z) : _
Where:
x: first axis or signal componenty: second axis or signal componentz: third axis or signal component
(mo.)totalEnvelope
Magnitude-based envelope across three axes.
Usage
totalEnvelope(thr, gain, envUpMs, envDownMs, x, y, z) : _
Where:
thr: threshold passed tomotionEnvelopegain: gain passed tomotionEnvelopeenvUpMs: attack time in milliseconds passed tomotionEnvelopeenvDownMs: release time in milliseconds passed tomotionEnvelopex: normalized X-axis signaly: normalized Y-axis signalz: normalized Z-axis signal
The three-axis magnitude is computed with pita3 before applying motionEnvelope.
Acceleration Envelopes
(mo.)accelEnvelopeAbs
Envelope follower on the absolute value of an accelerometer axis.
Usage
accelEnvelopeAbs(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: accelerometer axis signal (normalized, typically [-1, 1])
Output envelope is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.accelEnvelopeAbs(0.1, 1.2, 10, 12, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
accelEnvelopeAbs_test =
mo.accelEnvelopeAbs(0.1, 1.2, 10, 12, os.sawtooth(0.5));
(mo.)accelEnvelopePos
Envelope follower for positive acceleration on one axis.
Usage
accelEnvelopePos(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: accelerometer axis signal (normalized, typically [-1, 1])
Negative values are ignored; output envelope is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.accelEnvelopePos(0.05, 1.35, 10, 10, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
accelEnvelopePos_test =
mo.accelEnvelopePos(0.05, 1.35, 10, 10, os.triangle(2) * 0.8);
(mo.)accelEnvelopeNeg
Envelope follower for negative acceleration on one axis.
Usage
accelEnvelopeNeg(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: accelerometer axis signal (normalized, typically [-1, 1])
Positive values are ignored; output envelope is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.accelEnvelopeNeg(0.05, 1.35, 10, 10, accX);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
accelEnvelopeNeg_test =
mo.accelEnvelopeNeg(0.05, 1.35, 10, 10, os.triangle(2) * (-0.8));
(mo.)totalAccel
Total acceleration magnitude with thresholding and envelope.
Usage
totalAccel(thr, gain, envUpMs, envDownMs, ax, ay, az) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)ax: accelerometer X axis (normalized, typically [-1, 1])ay: accelerometer Y axis (normalized, typically [-1, 1])az: accelerometer Z axis (normalized, typically [-1, 1])
Output magnitude is clamped to [0, 1].
Example
mo = library("motion.lib");
fi = library("filters.lib");
dc(x) = x : fi.dcblockerat(10); // drop gravity, keep movement
process = mo.totalAccel(0.1, 1.35, 10, 10, dc(ax), dc(ay), dc(az));
Test
mo = library("motion.lib");
os = library("oscillators.lib");
totalAccel_test =
mo.totalAccel(0.05, 1.2, 8, 12,
os.sawtooth(0.2) * 0.2,
os.triangle(0.15) * 0.1,
os.sawtooth(0.12) * 0.3);
(mo.)totalAccelRange
Total acceleration magnitude, dead-zone given by its two endpoints.
Usage
totalAccelRange(lo, hi, envUpMs, envDownMs, ax, ay, az) : _
Where:
lo: magnitude at which the output leaves 0 (0..1)hi: magnitude at which the output reaches 1 (must be >lo)envUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)ax: accelerometer X axis (normalized, typically [-1, 1])ay: accelerometer Y axis (normalized, typically [-1, 1])az: accelerometer Z axis (normalized, typically [-1, 1])
totalAccel expressed with motionEnvelopeRange. A narrow span close to 1
makes a shock detector: totalAccelRange(0.999, 1, ...) fires only on the
sharpest peaks, where the equivalent totalAccel call needs a gain of 1000.
Example
mo = library("motion.lib");
fi = library("filters.lib");
dc(x) = x : fi.dcblockerat(10); // drop gravity, keep movement
process = mo.totalAccelRange(0.1, 0.8, 10, 10, dc(ax), dc(ay), dc(az));
Test
mo = library("motion.lib");
os = library("oscillators.lib");
totalAccelRange_test =
mo.totalAccelRange(0.1, 0.4, 8, 12,
os.sawtooth(0.2) * 0.2,
os.triangle(0.15) * 0.1,
os.sawtooth(0.12) * 0.3);
(mo.)totalAccelUD
Total acceleration magnitude with a peak detector and a symmetric smoother.
Usage
totalAccelUD(thr, gain, envUpMs, envDownMs, ax, ay, az) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: smoothing time constant in milliseconds, applied in both directions (>= 0)envDownMs: peak-decay time constant in milliseconds (>= 0)ax: accelerometer X axis (normalized, typically [-1, 1])ay: accelerometer Y axis (normalized, typically [-1, 1])az: accelerometer Z axis (normalized, typically [-1, 1])
totalAccel expressed with motionEnvelopeUD, for the rounder two-pole decay
described there. Suited to sustained gestures (wind, shaking) where the
switching follower of totalAccel cuts the tail off abruptly.
Example
mo = library("motion.lib");
fi = library("filters.lib");
dc(x) = x : fi.dcblockerat(10); // drop gravity, keep movement
process = mo.totalAccelUD(0.1, 1.35, 670, 0, dc(ax), dc(ay), dc(az));
Test
mo = library("motion.lib");
os = library("oscillators.lib");
totalAccelUD_test =
mo.totalAccelUD(0.05, 1.2, 120, 40,
os.sawtooth(0.2) * 0.2,
os.triangle(0.15) * 0.1,
os.sawtooth(0.12) * 0.3);
Gyroscope Envelopes
(mo.)gyroEnvelopeAbs
Envelope follower on the absolute value of a gyroscope axis.
Usage
gyroEnvelopeAbs(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: gyroscope axis signal (normalized rad/s range)
Output envelope is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.gyroEnvelopeAbs(0.01, 0.8, 50, 50, gx);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
gyroEnvelopeAbs_test =
mo.gyroEnvelopeAbs(0.02, 0.9, 25, 30, os.sawtooth(0.5) * 0.2);
(mo.)gyroEnvelopePos
Envelope follower for positive gyroscope rotation on one axis.
Usage
gyroEnvelopePos(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: gyroscope axis signal (normalized rad/s range)
Negative values are ignored; output envelope is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.gyroEnvelopePos(0.01, 0.8, 50, 50, gx);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
gyroEnvelopePos_test =
mo.gyroEnvelopePos(0.02, 0.9, 25, 30, os.triangle(3) * 0.8);
(mo.)gyroEnvelopeNeg
Envelope follower for negative gyroscope rotation on one axis.
Usage
gyroEnvelopeNeg(thr, gain, envUpMs, envDownMs, sig) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)sig: gyroscope axis signal (normalized rad/s range)
Positive values are ignored; output envelope is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.gyroEnvelopeNeg(0.01, 0.8, 50, 50, gx);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
gyroEnvelopeNeg_test =
mo.gyroEnvelopeNeg(0.02, 0.9, 25, 30, os.triangle(3) * (-0.8));
(mo.)totalGyro
Total gyroscope magnitude with thresholding and envelope.
Usage
totalGyro(thr, gain, envUpMs, envDownMs, gx, gy, gz) : _
Where:
thr: threshold subtracted before detection (normalized)gain: linear gain applied after thresholdingenvUpMs: attack time in milliseconds (>= 0)envDownMs: release time in milliseconds (>= 0)gx: gyroscope X axis (normalized rad/s range)gy: gyroscope Y axis (normalized rad/s range)gz: gyroscope Z axis (normalized rad/s range)
Output magnitude is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.totalGyro(0.01, 0.8, 50, 50, gx, gy, gz);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
totalGyro_test =
mo.totalGyro(0.02, 0.9, 25, 30,
os.sawtooth(0.2) * 0.2,
os.triangle(0.15) * 0.1,
os.sawtooth(0.12) * 0.3);
Orientation Weighting
(mo.)orientationWeight
Weighting of a 3D vector toward a target axis with shape and smoothing.
Usage
orientationWeight(targetX, targetY, targetZ, shape, xs, ys, zs, smoothMs) : _
Where:
targetX: target X coordinate (-1..1)targetY: target Y coordinate (-1..1)targetZ: target Z coordinate (-1..1)shape: scaling applied to the distance (>= 0, larger tightens the lobe)xs: current X coordinate (normalized)ys: current Y coordinate (normalized)zs: current Z coordinate (normalized)smoothMs: smoothing time in milliseconds (>= 0)
Output weight is clamped to [0, 1].
Example
mo = library("motion.lib");
process = mo.orientationWeight(0, 1, 0, 1, x, y, z, 10);
Test
mo = library("motion.lib");
orientationWeight_test =
mo.orientationWeight(0, 1, 0, 1, 0, 1, 0, 10);
(mo.)orientation6
Weights toward the six device axes (Cour/stage left -X, Rear -Y, Jardin/stage right +X, Front +Y, Down -Z, Up +Z).
Usage
orientation6(xs, ys, zs,
shapeCour, shapeRear, shapeJardin, shapeFront, shapeDown, shapeUp,
smoothMs) : _
Where:
xs: current X coordinate (normalized)ys: current Y coordinate (normalized)zs: current Z coordinate (normalized)shapeCour: shape for Cour (-X)shapeRear: shape for Rear (-Y)shapeJardin: shape for Jardin (+X)shapeFront: shape for Front (+Y)shapeDown: shape for Down (-Z)shapeUp: shape for Up (+Z)smoothMs: smoothing time in milliseconds (>= 0)
Output is six weights (Cour, Rear, Jardin, Front, Down, Up), each in [0, 1].
Example
mo = library("motion.lib");
process = mo.orientation6(x, y, z, 1, 1, 1, 1, 1, 1, 10);
Test
mo = library("motion.lib");
os = library("oscillators.lib");
orientation6_test =
mo.orientation6(
os.triangle(0.05),
os.sawtooth(0.08),
os.triangle(0.03),
1, 1, 1, 1, 1, 1,
10);
Utility Scaling
(mo.)scale
Normalized scaler with input dead-zone and bounded output range.
Usage
scale(ilow, ihigh, olow, ohigh) : _
Where:
ilow: minimum input value before scaling starts (0..1)ihigh: maximum input value before clamping (must be > ilow)olow: minimum output valueohigh: maximum output value
Inputs below ilow clamp to olow; above ihigh clamp to ohigh. A very narrow
span is honored: scale(0.999, 1, 0, 1) really does spread the last
thousandth of the input over the whole output range, which is what turns the
helper into a shock detector. Degenerate spans are absorbed by the divisor
floor rather than by moving ilow: ihigh <= ilow leaves the output pinned
at olow instead of scaling by a wrong factor.
Example
mo = library("motion.lib");
process = _ : mo.scale(0.2, 0.8, 100, 20000) : _;
Test
mo = library("motion.lib");
os = library("oscillators.lib");
scale_test = (os.sawtooth(2) * 0.5 + 0.5) : mo.scale(0.2, 0.8, 0, 1);