You can trigger vibrations on Meta Quest controllers using the xrApplyHapticFeedback() function in OpenXR. You can either use simple haptics with the XrHapticVibration struct from the OpenXR core specification, or use the parametric, PCM or amplitude envelope haptics extensions for more advanced haptic effects. This page describes these extensions, as well as a way to trigger localized haptics on the thumb and trigger haptic elements of Meta Quest Touch Pro controllers.
Note that changing the vibration frequency with the simple haptics API is not supported, the XrHapticVibration::frequency member is ignored. To trigger high-fidelity haptic effects that allow varying the vibration frequency, use the parametric haptics or PCM haptics API. Varying vibration frequency is supported for controllers with a VCM (Voice Coil Motor), which is Meta Quest Touch Pro, Touch Plus and later. Out of the two APIs, parametric haptics is recommended for most use cases due to its ease of use, wider availibility in other OpenXR runtimes, and device-agnostic data format.
Parametric haptics
Overview
With the parametric haptics API, you can trigger a high-fidelity vibration with an intensity and frequency that vary over time. The vibration is described in a device-agnostic format.
The parametric haptics API is available with the XR_EXT_haptic_parametric OpenXR extension. Read its specification for a detailed description of the API and more
example code.
Data format
A parametric haptics vibration is described by a series of amplitude points, frequency points, and transients.
The amplitude points describe how the intensity of the vibration changes over time.
The frequency points describe how the frequency of the vibration changes over time, which is supported by Meta Quest Touch Pro, Touch Plus and later controllers. Frequency points are ignored for older controllers.
A transient is a short burst that has a strong and “clicky” characteristic. Transients are useful for adding a layer of distinct, discernible, and emphasized points to the resulting vibration.
The parametric haptics data is translated to a signal that drives the haptic motor. Meta Quest Touch Pro, Touch Plus and later controllers have a voice coil motor (VCM), which is driven by a PCM waveform. Previous controllers like the Meta Quest Touch have a linear resonant actuator (LRA), which is driven by stepped amplitude changes. On these controllers, the frequency points are ignored, as they vibrate at a fixed frequency.
Setup
Include the openxr.h header file from the Khronos OpenXR SDK:
#include <openxr/openxr.h>
Check that the extension is supported on the headset by calling xrGetSystemProperties() and checking XrSystemHapticParametricPropertiesEXT::supportsParametricHaptics. Headsets before Quest 2 do not support parametric haptics.
XrSystemHapticParametricPropertiesEXT systemHapticParametricProperties{
XR_TYPE_SYSTEM_HAPTIC_PARAMETRIC_PROPERTIES_EXT};
XrSystemProperties systemProperties = {XR_TYPE_SYSTEM_PROPERTIES,
&systemHapticParametricProperties};
xrGetSystemProperties(instance, systemId, &systemProperties);
if (systemHapticParametricProperties.supportsParametricHaptics == XR_FALSE) {
// Parametric haptics not supported, don't use it
}
Triggering a parametric haptics vibration
You can trigger a parametric haptics vibration by making one call to the xrApplyHapticFeedback() function and passing the amplitude points, frequency points, and transients of the entire vibration:
The values for amplitude points, frequency points, and transients range from 0.0 to 1.0. The time values for these points are in nanoseconds since the start of the haptic vibration. The first amplitude point needs to be at time 0ns. Frequency points and transients are optional.
You can either define the amplitude points, frequency points, and transients in code, or use Meta Haptics Studio, export the haptic clip as a .haptic JSON file, and then read the data from that file.
Streaming
While you can trigger a vibration by passing the entire data upfront in one call to xrApplyHapticFeedback(), in some cases you need multiple calls to xrApplyHapticFeedback() over time, in which the data is passed piece-by-piece. This is called streaming. Streaming is needed in these cases:
When not all of the haptic data is known upfront, and is generated on-the-fly instead.
When the haptic data contains more than the maximum of XR_HAPTIC_PARAMETRIC_MAX_POINTS_TRANSIENTS_EXT amplitude points, frequency points, or transients.
The haptic data passed in one API call is called a haptic frame. In the initial call to the API, you pass the first frame of haptic data. Before that frame has been fully played out, you call the API again with a new frame of haptic data. The first frame needs to contain at least two amplitude points, later frames need to contain at least one. For each call, set the streamFrameType member to the appropriate frame type. The example code above does not use streaming, so the frame type is set to XR_HAPTIC_PARAMETRIC_STREAM_FRAME_TYPE_NONE_EXT.
Call the xrHapticParametricGetPropertiesEXT() function to query the minimum duration the first frame needs to have, as well as the optimal timing interval for sending subsequent frames:
The minimum duration the first frame needs to have is available in XrHapticParametricPropertiesEXT::minimumFirstFrameDuration, and the optimal timing interval for sending subsequent frames is available in XrHapticParametricPropertiesEXT::idealFrameSubmissionRate.
Absolute frequencies
The amplitude and frequency values range from 0.0 to 1.0, which are automatically mapped to the full intensity and frequency range supported by the controller.
For frequencies, you can also specify the absolute frequency range in Hertz. The absolute frequency range is specified in the first frame, and used for the entire haptic vibration. To specify the absolute frequency range, set minFrequencyHz and maxFrequencyHz to the respective values. The example code above uses the maximum frequency range supported by the controller, so both values are set to XR_FREQUENCY_UNSPECIFIED.
To query the maximum frequency range supported by the controller, call xrHapticParametricGetPropertiesEXT(). The supported frequency range is available in the minFrequencyHz and maxFrequencyHz members of XrHapticParametricPropertiesEXT.
PCM Haptics
With the PCM haptics API, you can trigger a vibration that is described by a PCM (Pulse Code Modulation) waveform. For controllers with a VCM (Meta Quest Touch Pro, Touch Plus and later), the PCM waveform directly drives the haptic motor. For other controllers (Meta Quest Touch and earlier), an equivalent haptic effect is played.
The PCM haptics API is available with the XR_FB_haptic_pcm OpenXR extension. Read its specification for a detailed description of the API.
To trigger a PCM haptics vibration, pass a XrHapticPcmVibrationFB struct to xrApplyHapticFeedback(). The struct contains a buffer with the PCM waveform that describes the vibration pattern. Here is an example of how to create a XrHapticPcmVibrationFB struct and pass it to xrApplyHapticFeedback:
The above example uses a sample rate of 2000Hz for the generated haptic signal, and the system resamples the waveform to the sample rate of the controller. If you prefer to match the signal sample rate to that of the controller (saving the system the need to resample), use the sample rate returned by the xrGetDeviceSampleRateFB() function to generate the haptic data:
If the controller does not support PCM haptics, xrGetDeviceSampleRateFB() will return 0. The returned value will change if the connected controller changes, so call this function whenever necessary to ensure your application has the latest information on the device sample rate.
Amplitude Envelope
With the amplitude envelope haptics API, you can trigger a vibration with an intensity that varies over time. How the intensity changes over time is described in the amplitude envelope that is passed upfront in a single API call.
Consider the following complex analog signal.
The amplitude envelope of a signal is a smooth curve outlining its extremes. Amplitude envelope for the above signal would look like this:
The amplitude envelope haptics API is available with the XR_FB_haptic_amplitude_envelope OpenXR extension. Read its specification for a detailed description of the API.
To trigger an amplitude envelope haptics vibration, pass a XrHapticAmplitudeEnvelopeVibrationFB struct to xrApplyHapticFeedback(). The struct contains a buffer of amplitudes. Here is an example of how to create an XrHapticAmplitudeEnvelopeVibrationFB struct and pass it to xrApplyHapticFeedback:
On the Meta Quest Touch Pro controller, there are three haptic elements: one VCM (Voice Coil Motor) and two LRAs (Linear Resonant Actuators) for the thumb and trigger. The LRAs can be triggered using simple haptics only, and vibrate at a fixed frequency.
The localized haptic elements are available in the XR_FB_touch_controller_pro interaction profile. Read its specification for a detailed description.
Here is an example of how to trigger a vibration on the thumb haptic element of the left controller for one second:
There is a known limitation in the current release: If haptics is triggered on the thumb LRA and on both the controllers using XR_PATH_NULL in subactionPath of XrHapticActionInfo, then haptics are played for double the time on the right controller’s thumb LRA. To mitigate this, the application will have to call xrStopHapticFeedback on the right controller.