⚠ Preview docs — built from the develop branch and may be unstable. Go to the stable docs.

Input.HapticWaveform

static void HapticWaveform(InputHaptic output, Single[] samples, float sampleRateHz, bool append)

Plays a PCM waveform on the given output. Samples are signed values in [-1, 1] sampled at sampleRateHz, like an audio buffer. Requires the InputHapticCaps.Waveform capability bit; the call is a silent no-op on devices that lack XR_FB_haptic_pcm.

Buffers longer than the runtime’s per-call limit are streamed transparently — pass any length and StereoKit will drip-feed chunks to the device over multiple frames. For procedural use, reuse the same float[] buffer across calls to avoid per-frame allocations. To explicitly cut a stream mid-playback, pass append=false here, or call HapticStop / HapticPulse.

   
InputHaptic output Which haptic output to vibrate.
Single[] samples Signed [-1, 1] PCM samples.
float sampleRateHz The sample rate the buffer was authored at. See HapticPreferredRate for the device’s native rate. If this differs from the in-flight stream’s rate, append behavior degrades to a restart.
bool append When true, queues these samples after any playback already in flight on this output. When false (default), cancels any current playback and starts the new buffer immediately.
static void HapticWaveform(InputHaptic output, Single[] samples, float sampleRateHz, bool append, Int32& prevSamplesConsumed)

Plays a PCM waveform and reports how many samples the runtime had drained from the previously-submitted chunk by the time this call was processed. Streaming callers use the consumed count to throttle their per-frame submission rate and bound playback latency.

   
InputHaptic output Which haptic output to vibrate.
Single[] samples Signed [-1, 1] PCM samples.
float sampleRateHz The sample rate the buffer was authored at.
bool append When true, queues after any in-flight playback; when false, replaces it.
Int32& prevSamplesConsumed Receives the runtime’s samplesConsumed count for the most recent chunk StereoKit submitted to the device, or 0 if no chunk was in flight. This is reported at chunk granularity, not at the granularity of HapticWaveform calls — a single HapticWaveform call may span multiple internal chunks.

Examples

Driving haptics from controller velocity

This shows how to map a continuous physical signal (here, the controller’s grip-pose velocity) onto haptic output. There are two paths: a simple per-frame HapticPulse that works on every device, and a streaming HapticWaveform path that’s used when XR_FB_haptic_pcm is available.

void StepProcedural(InputHaptic output)
{
	Handed   hand    = output == InputHaptic.LController ? Handed.Left : Handed.Right;
	InputPose pose   = output == InputHaptic.LController ? InputPose.LGrip : InputPose.RGrip;
	PoseState state  = Input.PoseState(pose);
	if (!state.IsTracked()) { haveLastPos = false; return; }

	Vec3 pos = Input.Pose(pose).position;
	if (!haveLastPos) { lastGripPos = pos; haveLastPos = true; return; }

	float speed   = (pos - lastGripPos).Length / Math.Max(0.001f, Time.Stepf);
	lastGripPos   = pos;
	float intensity = MathF.Min(1, speed / 2.0f); // ~2 m/s saturates

	InputHapticCaps caps = Input.HapticCaps(output);
	if ((caps & InputHapticCaps.Waveform) != 0)
	{
		// Streaming path: synthesize one frame's worth of samples at the
		// device's preferred rate, append onto the existing stream.
		float r     = Input.HapticPreferredRate(output);
		if (r <= 0) r = 4000;
		int   count = (int)(r * Time.Stepf);
		if (procBuffer.Length != count) procBuffer = new float[count];
		for (int i = 0; i < count; i++)
			procBuffer[i] = MathF.Sin(2 * MathF.PI * 220 * i / r) * intensity;
		Input.HapticWaveform(output, procBuffer, r, append: true);
	}
	else if ((caps & InputHapticCaps.Pulse) != 0)
	{
		// Fallback path: per-frame pulse with current intensity.
		Input.HapticPulse(output, 0, intensity, Time.Stepf);
	}
}




Found an issue with these docs, or have some additional questions? Create an Issue on Github!