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:

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 below offset clamp 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 thresholding
  • envUpMs: 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 thresholding
  • envUpMs: 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 to motionEnvelope
  • sig: 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 to motionEnvelope
  • sig: 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 to motionEnvelope
  • sig: 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 component
  • y: second axis or signal component
  • z: 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 to motionEnvelope
  • gain: gain passed to motionEnvelope
  • envUpMs: attack time in milliseconds passed to motionEnvelope
  • envDownMs: release time in milliseconds passed to motionEnvelope
  • x: normalized X-axis signal
  • y: normalized Y-axis signal
  • z: 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 thresholding
  • envUpMs: 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 thresholding
  • envUpMs: 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 thresholding
  • envUpMs: 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 thresholding
  • 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])

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 thresholding
  • envUpMs: 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 thresholding
  • envUpMs: 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 thresholding
  • envUpMs: 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 thresholding
  • envUpMs: 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 thresholding
  • envUpMs: 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 value
  • ohigh: 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);