Skip to content

ponoor/max-trapezoidal-motion

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

max-trapezoidal-motion

A trapezoidal motion profile simulator for Cycling'74 Max (v8 JavaScript engine).

Simulates the acceleration → cruise → deceleration shape of a real stepper motor, producing smooth position and velocity curves you can use to drive lights, audio parameters, or any time-varying value in your patcher. Behavioral reference: STMicroelectronics L6470 stepper driver.

image

Setup

  1. Copy motion-profile.js and trapezoid.js into your Max project folder (or the Max search path).
  2. Create a [v8 motion-profile.js] object in your patcher.
  3. Connect the four outlets as described below.

Both .js files must be in the same folder — motion-profile.js loads trapezoid.js automatically.


Outlets

The object has four outlets (left to right):

# Type Contents
0 float Position — current position during streaming, or total duration (ms) after calc
1 float Velocity — current velocity (signed); 0 when idle
2 message Status — phase, profile data, state messages, and errors
3 bang Done — fires when motion completes (or stops)

Units

Parameter Unit
position / target dimensionless — you decide what it means (steps, mm, degrees…)
acc / dec position units per second² (e.g. 1000 = 1000 units/s²)
maxSpeed position units per second
Duration output (outlet 0 / status messages) milliseconds
query time argument milliseconds
interval argument milliseconds

All motion math is internally in seconds; durations are converted to milliseconds on output so they integrate naturally with Max's timing ecosystem.


Messages In

calc — Instant profile calculation

Calculates the motion profile and returns timing immediately. No streaming. Use this to pre-plan timing for other events.

calc <target> <acc> <dec> <maxSpeed>
Argument Description
target Destination position (absolute)
acc Acceleration (units/s²)
dec Deceleration (units/s²)
maxSpeed Maximum speed (units/s)

Outputs:

  • Outlet 0: total duration in ms
  • Outlet 2: profile_calc <totalDuration> <accelDuration> <cruiseDuration> <decelDuration> <peakSpeed> <distance> (all durations in ms)
  • Outlet 3: bang

The current position is advanced to target instantly. peakSpeed may be less than maxSpeed if the distance is too short to reach full speed (triangular profile).

Example:

calc 1000 500 300 800

→ outlet 0: 2083.33 (total duration in ms) → outlet 2: profile_calc 2083.33 1600.0 0.0 2666.67 800.0 1000.0


move — Real-time streaming motion

Starts a motion and streams position + velocity at a regular interval (default: every 10 ms).

move <target> <acc> <dec> <maxSpeed>

Same arguments as calc.

Outputs per tick:

  • Outlet 0: current position
  • Outlet 1: current velocity (positive = moving toward higher positions, negative = lower)
  • Outlet 2: phase accel / phase cruise / phase decel

On completion:

  • Outlet 2: done
  • Outlet 3: bang

Sending move while already moving (dynamic re-targeting):

The object re-plans intelligently without stopping:

  • Same direction → seamless blend. The new profile starts from current position and velocity; no jerk or pause.
  • Reverse direction → decelerates to a stop first (using the current decel rate), then starts the new move.

Outlet 2 announces: retarget <newTarget> blend or retarget <newTarget> reverse


query — Sample a frozen profile at a given time

Reads position and velocity from the last stored profile at an arbitrary time, without actually running the motion. Use this when you want to drive the motion from Max's transport or an external clock.

query <time>
Argument Description
time Time offset from motion start, in milliseconds

Outputs:

  • Outlet 0: position at that time
  • Outlet 1: velocity at that time
  • Outlet 2: phase accel / phase cruise / phase decel / phase done

Does not change currentPosition. Values are clamped: times below 0 return the start, times past the end return the target.

Requires a profile to be stored first (via calc or move). If none: error no_profile.

query is used instead of eval because eval is reserved by the Max v8 engine.


stop — Controlled deceleration stop

stop

Decelerates to zero using the current deceleration rate. When fully stopped:

  • Outlet 2: stopped
  • Outlet 3: bang
  • currentPosition is updated to the actual stop point

No effect if idle.


hardStop — Immediate halt

hardStop

Freezes position instantly with no deceleration. Use for emergency situations.

  • Outlet 2: hardstop
  • Outlet 3: bang

setPos — Set position manually

setPos <position>

Sets currentPosition to the given value without moving. Only works when idle. Useful for calibration or repositioning the logical origin.

Error if called while running: error cannot_setpos_while_running


getPos — Read current position

getPos

Outputs currentPosition on outlet 0.


getVel — Read current velocity

getVel

Outputs currentVelocity on outlet 1.


getState — Read full state

getState

Outlet 2: state <position> <velocity> <isRunning> <phase>

phase is one of: idle accel cruise decel stopping


getProfile — Read stored profile data

getProfile

Outlet 2: profile <startPos> <targetPos> <totalDuration> <accelDuration> <cruiseDuration> <decelDuration> <peakSpeed>

All durations in milliseconds. Error if no profile is stored: error no_profile


interval — Set streaming tick rate

interval <ms>

Sets the timer interval for move streaming. Default: 10 ms. Minimum: 1 ms.

Takes effect on the next move command (not mid-motion).


reset — Reset all state

reset
  • Stops any running motion (hard stop)
  • Sets position and velocity to 0
  • Clears stored profile
  • Resets interval to 10 ms
  • Outlet 2: reset

Status Messages (Outlet 2) — Quick Reference

Message When
profile_calc <totalMs> <accelMs> <cruiseMs> <decelMs> <peakSpeed> <distance> After calc
phase <accel|cruise|decel> Each streaming tick
done Motion completed normally
retarget <target> <blend|reverse> New move during active motion
stopped Controlled stop completed
hardstop Hard stop executed
state <pos> <vel> <running> <phase> Response to getState
profile <startPos> <targetPos> <totalMs> <accelMs> <cruiseMs> <decelMs> <peakSpeed> Response to getProfile
reset After reset
error <code> Error (see below)

Error Codes

Code Cause
no_profile query or getProfile called with no stored profile
cannot_setpos_while_running setPos called during active motion
invalid_params Missing or non-numeric arguments
acc_must_be_positive acc ≤ 0
dec_must_be_positive dec ≤ 0
maxspeed_must_be_positive maxSpeed ≤ 0

Tips

  • Units are yours to define. Position values are dimensionless — they could be MIDI notes, millimeters, degrees, or DMX levels. Just be consistent with your acc/dec/maxSpeed scale.
  • Asymmetric profiles are fine. Acceleration and deceleration can differ independently (e.g. fast ramp-up, slow ramp-down).
  • Zero-distance move (target == current position): completes immediately with a done bang and no streaming.
  • Pre-plan with calc, then re-use with query. Call calc once to store the profile, then scrub through it with query from a [transport] or [counter].
  • Dynamic re-targeting is intentional. Send move again at any time — the engine re-plans smoothly. There is no command queue; just send a new destination whenever your logic changes.

About

A trapezoidal motion emulator for MaxMSP

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors