Skip to content

2. Lofelt SDK for iOS

João Freire edited this page Aug 10, 2022 · 2 revisions

Using Lofelt SDK for iOS

Lofelt has developed the an iOS framework to simplify the integration of haptics into applications. The applications you create with this framework will work on iPhones that are compatible with Core Haptics.

The iOS Framework provides a mechanism for playing a pre-authored haptic clip. The goal is to be used when you want a haptic effect to match a sound file used in your app. For example, when you want to play the haptic effect of a door closing simultaneously with the sound of the door closing.

Adding the iOS Framework for iOS to Your Xcode Project

After extracting the downloaded Lofelt SDK .zip file, open the sdk folder, then the ios-framework folder. Inside this folder are two more folders: Xcode11 and Xcode12AndHigher. If you are using Xcode 12 or above, import the LofeltHaptics.xcframework to your project. If you are using an older version of Xcode, choose the framework based on what you want to do:

I am using Xcode 11 and only want to debug my app on iPhones and/or I want to release my app on Apple's AppStore I am using Xcode 11 and only want to debug using the Xcode simulator
Take the LofeltHaptics.framework from the devices folder. Take the LofeltHaptics.framework from the simulator folder.

Note: Apple suggests using XCFrameworks for the latest development and will reject an iOS app containing a .framework with support for the Xcode simulator. So, only the XCFramework and devices versions can be included in apps intended for App Store submission and distribution.

Initializing the Studio Framework for iOS

To use Lofelt SDK haptics in your app, you must first create a LofeltHaptics object. You will then be able to issue haptic commands to that object. Note that all of the following code examples are written in Swift.

import LofeltHaptics

let haptics = try? LofeltHaptics.init()

You should have only one LofeltHaptics object in your application. At the time of this writing, this means the LofeltHaptics object you create is capable of playing back one haptic clip at a time or converting one audio stream to haptics at a time.

Playing Haptic Clips

A haptic clip contains high-definition, device-agnostic haptic data that the Studio framework for iOS uses to create an optimal haptic effect on playback devices. The playback of these haptic clips requires only three simple steps:

  1. Load the haptic clip into the LofeltHaptics object with the load() method.
  2. Start playback of the haptic clip with the play() method.
  3. Stop playback of the haptic clip with the stop() method (which is only needed when you wish to stop the haptic clip before it has finished playing).

Checking if device meets haptic requirements

func deviceMeetsMinimumRequirements() -> Bool

The deviceMeetsMinimumRequirements() method allows you to easily check if the iOS device meets the minimum requirements to playback haptic clips. At the moment, the iOS minimum requirements are:

  • iOS 13+
  • iPhone 8 or newer

Load

func load(data: String) throws

The load() method allows you to load the contents of a haptic clip into memory before triggering playback with play(). This preloading procedure is crucial for achieving tight synchronization between a haptic clip and its associated audio sample.

Play

func play() throws

The play() method initiates haptic playback of the haptic clip currently loaded into the LofeltHaptics instance.

Playback of the haptic effect always starts from the beginning of the haptic clip and automatically stops when playback reaches the end of the haptic clip. However, you can stop playback of the haptic effect prematurely with the stop() method.

Stop

func stop() throws

If the LofeltHaptics object is currently playing a haptic clip, the stop() method terminates playback. If a haptic clip is not playing, the stop() method will have no effect.

Using the stop() method leaves the currently loaded haptic clip in memory. A subsequent play() method will play the same haptic clip again starting from the beginning.

Seek

func seek(time: float) throws

Once a haptic clip has been loaded with load(), the seek() method moves playback to the given time position within the haptic. The playback state (playing or stopped) will not be changed unless seeking beyond the end of the haptic clip which will stop playback. Seeking to a position before the start of the clip (a negative time position) will simply move playback to the start of the clip leaving the playback state unchanged.

Loop

func loop(enabled: bool) throws

This method allows the playback of a haptic clip to loop. This means playback will repeat from the beginning once it reaches the end.

When a new clip is loaded, looping is disabled.

If seek() is called while playing, playback will jump to the sought position and then repeat from the start.

Amplitude and frequency modulation

func setAmplitudeMultiplication(factor: float) throws
func setFrequencyShift(shift: float) throws

These methods allow you to modulate the amplitude and frequency of a clip.

The amplitude of a clip is modulated by multiplying each breakpoint's amplitude by the given multiplication factor. The multiplication factor must be 0 or greater. The frequency of a clip is modulated by adding the frequency shift to each breakpoint's frequency. The frequency shift needs to be between -1.0 and 1.0.

If the resulting amplitude or frequency is smaller than 0.0 or larger than 1.0, it will be clipped to that range. Clipping can cause artifacts, so you should take care to avoid excessive clipping. For example, if the breakpoints in a clip already have a high amplitude, a high amplitude multiplication factor should be avoided.

The modulation is applied right away, even to a currently playing clip.

Clip duration

func getClipDuration() -> float

Once a haptic clip has been loaded with load(), it is possible to get its duration in seconds. If no clip is loaded and getClipDuration is called, the returned value will be 0.0.

Suspend and resume

Haptic output from an app stops when it is put into the background. However, haptics that were interrupted when the app was put into background do not automatically resume and need to be started again by calling play().

Code Examples

Play haptic with audio

let haptics = try? LofeltHaptics.init()

// Fetch a Haptic Clip from the asset catalog.
let hapticClip = NSDataAsset(name: "haptic_clip.haptic")

// Load its data into an NSString.
let hapticData = NSString(data: hapticClip!.data , encoding: String.Encoding.utf8.rawValue)

// Load it into the LofeltHaptics object as a String.
try? haptics.load(hapticData! as String)

// Play audio and haptics (audio must be played first).
audioPlayer?.play()
haptics?.play()

Where:

  • hapticData is of type String and is the contents of a haptic clip created with Studio.
  • audioPlayer is some kind of audio player, for example, AVAudioPlayer.

It is important to play the audio before the haptic effect for optimal audio/haptic synchronicity.

ℹ️ There are more advanced ways to achieve audio/haptics sync that the iOS framework currently doesn't implement. Currently, the framework uses the "trigger-and-hope-for-the-best" approach.

Seek to a position in haptic

let haptics = try? LofeltHaptics.init()

// Fetch a Haptic Clip from the asset catalog.
let hapticClip = NSDataAsset(name: "haptic_clip.haptic")

// Load its data into an NSString.
let hapticData = NSString(data: hapticClip!.data , encoding: String.Encoding.utf8.rawValue)

// Load it into the LofeltHaptics object as a String.
try? haptics.load(hapticData! as String)

haptics?.play()

try! haptics.seek(1.0)

Where:

  • hapticData is of type String and is the contents of a haptic clip created with Studio.

Example Project

In the sdk/examples/ios folder within the Lofelt SDK download, there is an example project for playing haptic clips on iPhone called LofeltHapticsExamplePreAuthored. To run the example, open the project in Xcode, target your iPhone, and build. The example provides two buttons that, when pressed and released, will trigger audio files and accompanying haptic clips.

Troubleshooting

The pre-authored haptics are out of sync with the audio

Make sure you play the audio before the haptics for optimal synchronicity.

let haptics = try? LofeltHaptics.init()

haptics?.load(hapticData)
audioPlayer?.play()
haptics?.play()

Error Handling

You should wrap your calls into LofeltHaptics in try statements. See: https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html

Error Handling Example

do {
    try haptics.load(dataString! as String)
} catch let error as NSError {
    print("Received an error: (error.localizedDescription)")
}

You can also use the try? or try! variations.

Error Types

In the event you encounter an error message while using the iOS framework, the tips below can help resolve the issue. If the error persists even after trying these remedies, please open issue in this repository with the details of the error you receive.

Function Called Error Message Recovery Steps
init() LofeltHaptics initAndReturnError: Error initializing Core Haptics engine: <error details> Try running your app again. If it fails consistently, please report it as a bug.
init() LofeltHaptics: Unable to set realtime policy for the streaming thread.
Message is logged only, not returned in the NSError.
iOS was not able to raise the priority of the haptic streaming thread, which affects the timing of haptic playback. Please report it as a bug.
load() The haptic clip you are attempting to play is of a newer version than what is supported by this framework.
Message is logged only, not returned in the NSError. This is a warning, not an error.
Get the latest version of the iOS framework from GitHub releases and include this in your app.
load() Error loading haptic data: Error validating V0/V1: <error details> 1. Make sure you are following the .haptic file format.
2. Read the file correctly into a String with UTF-8 encoding.
load() Error loading haptic data: Error deserializing V0/V1: <error details> 1. Make sure you are following the .haptic file format.
2. Read the file correctly into a String with UTF-8 encoding.
load() Error loading haptic data: Unsupported version Get the latest version of the iOS framework from GitHub releases and include this in your app.
play() Error playing haptic clip: Unable to play, no clip loaded. Call play() only after a haptic clip was loaded without any errors.
stop() Error stopping haptic clip: Unable to stop, no clip loaded. Call stop() only after play().
setAmplitudeMultiplication() Error setting amplitude multiplication to <factor>: Unable to apply amplitude multiplication factor <factor>, needs to be 0 or greater Use a multiplication factor between 0.0 and ∞.
setAmplitudeMultiplication() Error setting amplitude multiplication to <factor>: Unable to set amplitude multiplication, no clip loaded. Call setAmplitudeMultiplication() only after a haptic clip was loaded without any errors.
setFrequencyShift() Error setting frequency shift to <shift>: Unable to apply frequency shift <shift>, needs to be between -1 and 1 Use a shift between -1.0 and 1.0.
setFrequencyShift() Error setting frequency shift to <shift>: Unable to set frequency shift, no clip loaded. Call setFrequencyShift() only after a haptic clip was loaded without any errors.
loop() Unable to loop, no clip loaded. Call loop() only after a haptic clip was loaded without any errors.
Playback of pre-authored haptic clips
These errors are logged during haptic playback only, and not returned from a function
LofeltHaptics: Could not play amplitude event: <error details> iOS was unable to play back a haptic event. Please report it as a bug.
Playback of pre-authored haptic clips
These errors are logged during haptic playback only, and not returned from a function
LofeltHaptics: Could not play frequency event: <error details> iOS was unable to play back a haptic event. Please report it as a bug.