buttplug.js

Examples

Complete real-world pattern recipes.


Timed Vibration

Play a preset for a fixed duration, then stop. The timeout option handles cleanup automatically.

const id = await engine.play(device, "wave", {
  intensity: 0.8,
  timeout: 5000,
  onStop: (patternId, reason) => {
    console.log(`Pattern ${patternId} stopped: ${reason}`);
  },
});
// Pattern auto-stops after 5 seconds with reason "timeout"

Multi-Motor Pattern

Drive two vibration motors at different intensities using separate custom tracks. Motor 0 ramps up while motor 1 pulses.

await engine.play(device, [
  {
    featureIndex: 0,
    keyframes: [
      { value: 0.2, duration: 0 },
      { value: 1, duration: 3000, easing: "easeIn" },
    ],
  },
  {
    featureIndex: 1,
    keyframes: [
      { value: 0, duration: 0 },
      { value: 0.6, duration: 500 },
      { value: 0, duration: 500 },
    ],
  },
], { loop: true });

Position Stroking with Custom Speed

Build a position stroke pattern with asymmetric timing -- slow extend, fast retract.

await engine.play(device, [
  {
    featureIndex: 0,
    outputType: "Position",
    keyframes: [
      { value: 0, duration: 0 },
      { value: 1, duration: 1500, easing: "easeInOut" },
      { value: 0, duration: 600, easing: "easeIn" },
    ],
  },
], { loop: true });

Pattern Lifecycle Callbacks

Use onComplete and onStop to react to pattern state changes. onComplete fires only when all loops finish naturally. onStop fires for every stop regardless of reason.

const id = await engine.play(device, "surge", {
  loop: 3,
  onComplete: (patternId) => {
    console.log(`Completed all cycles: ${patternId}`);
  },
  onStop: (patternId, reason) => {
    console.log(`Stopped (${reason}): ${patternId}`);
    // reason: "complete" | "manual" | "timeout" | "error"
    //       | "disconnect" | "deviceRemoved"
  },
});

onComplete fires before onStop. When a pattern completes naturally, both callbacks fire in sequence -- first onComplete, then onStop with reason "complete".

Dynamic Pattern Switching

Starting a new pattern on a device automatically stops any existing pattern on that device. There is no need to call stop() first.

// Start with a gentle wave
await engine.play(device, "wave", { intensity: 0.3 });

// Switch to intense pulse -- previous pattern auto-stops
await engine.play(device, "pulse", { intensity: 1.0 });

// Switch to a custom pattern
await engine.play(device, [
  {
    featureIndex: 0,
    keyframes: [
      { value: 0, duration: 0 },
      { value: 1, duration: 200 },
      { value: 0.5, duration: 100 },
      { value: 1, duration: 200 },
      { value: 0, duration: 500 },
    ],
  },
], { loop: true });

For the full options API, see the PatternEngine reference.

On this page