buttplug.js

Custom Tracks

Build patterns from keyframe sequences.


A Track binds a sequence of Keyframe values to a specific device feature. Pass an array of tracks as the second argument to engine.play() for full control over pattern shape.

Single Track

Each keyframe defines a target value (0-1), a duration in milliseconds for the transition, and an optional easing curve. A duration of 0 creates an instant jump.

const track = {
  featureIndex: 0,
  keyframes: [
    { value: 0, duration: 0 },
    { value: 1, duration: 500, easing: "easeIn" },
    { value: 0.2, duration: 300, easing: "easeOut" },
    { value: 0, duration: 200 },
  ],
};

await engine.play(device, [track]);

The total cycle duration is the sum of all keyframe durations in the track (1000ms above). Without loop: true, the pattern plays once and stops.

Multi-Track Patterns

Target multiple features simultaneously by including multiple tracks. Each track operates independently on its assigned featureIndex. The cycle duration is determined by the longest track -- shorter tracks hold their final value until the cycle ends.

await engine.play(device, [
  {
    featureIndex: 0,
    keyframes: [
      { value: 0, duration: 0 },
      { value: 1, duration: 1000 },
    ],
  },
  {
    featureIndex: 1,
    keyframes: [
      { value: 1, duration: 0 },
      { value: 0, duration: 1000 },
    ],
  },
]);

Output Type and Rotation

Set outputType explicitly to target a specific output type when a device has mixed feature types. Valid types are Vibrate, Rotate, RotateWithDirection, Oscillate, Constrict, Spray, Temperature, Led, Position, and HwPositionWithDuration. By default, the engine resolves tracks to compatible features automatically.

For RotateWithDirection features, set clockwise to control rotation direction (defaults to true).

const rotationTrack = {
  featureIndex: 0,
  outputType: "RotateWithDirection",
  clockwise: false,
  keyframes: [
    { value: 0, duration: 0 },
    { value: 1, duration: 2000, easing: "easeInOut" },
  ],
};

Looping and Intensity

Custom tracks do not loop by default. Pass loop: true for infinite repetition or a number for a fixed cycle count. The intensity option scales all keyframe values proportionally -- a keyframe with value: 1 at intensity: 0.5 produces an effective value of 0.5.

await engine.play(device, tracks, {
  loop: true,
  intensity: 0.6,
});

Combine looping with lifecycle callbacks to build sequences that react when cycles complete. See the PatternEngine reference for full Track and Keyframe type definitions.

On this page