Easily play combinations of sound effects and Taptic Engine vibrations on iOS

Piano

Piano is a convenient and easy-to-use wrapper around the AVFoundation and UIHapticFeedback frameworks, leveraging the full capabilities of the Taptic Engine, while following strict Apple guidelines to preserve battery life. Ultimately, Piano allows you, the composer, to conduct masterful symphonies of sounds and vibrations, and create a more immersive, usable and meaningful user experience in your app or game.

Compatibility

Piano requires iOS 10+ and is compatible with Swift 4 projects.

Installation

platform :ios, '10.0'
target 'ProjectName' do
use_frameworks!

    pod 'Piano', '~> 1.7'

end

(if you run into problems, pod repo update and try again)

github "saoudrizwan/Piano"

(make sure Xcode 9 is set as your system's default Xcode before using CocoaPods or Carthage with Swift 4 frameworks)

  • Or embed the Piano framework into your project

And import Piano in the files you'd like to use it.

Usage

Using Piano is simple.

let symphony: [Piano.Note] = [
    .sound(.asset(name: "acapella")),
    .hapticFeedback(.impact(.light)),
    .waitUntilFinished,
    .hapticFeedback(.impact(.heavy)),
    .wait(0.2),
    .sound(.system(.chooChoo))
]

Piano.play(symphony)

... or better yet:

?.play([
    .sound(.asset(name: "acapella"))
    ])

Optionally add a completion block to be called when all the notes are finished playing:

?.play([
    .sound(.asset(name: "acapella"))
]) {
    // ...
}

Or cancel the currently playing symphony:

?.cancel()

In the background, each note has an internal completion block, so you can add a .waitUntilFinished note that tells Piano to not play the next note until the previous note is done playing. This is useful for creating patterns of custom haptic feedback, besides the ones Apple predefined. This is also great for creating complex combinations of sound effects and vibrations.

Notes

.sound(Audio)

Plays an audio file.

Audio
.asset(name: String) Name of asset in any .xcassets catalogs. It's recommended to add your sound files to Asset Catalogs instead of as standalone files to your main bundle.
.file(name: String, extension: String) Retrieves a file from the main bundle. For example a file named Beep.wav would be accessed with .file(name: "Beep", extension: "wav").
.url(URL) This only works for file URLs, not network URLs.
.system(SystemSound) Predefined system sounds in every iPhone. See all available options here.

.vibration(Vibration)

Plays standard vibrations available on all models of the iPhone.

Vibration
.default Basic 1-second vibration
.alert Two short consecutive vibrations

.tapticEngine(TapticEngine)

Plays Taptic Engine vibrations available on the iPhone 6S and above.

TapticEngine
.peek One weak boom
.pop One strong boom
.cancelled Three sequential weak booms
.tryAgain One weak boom then one strong boom
.failed Three sequential strong booms

.hapticFeedback(HapticFeedback)

Plays Taptic Engine Haptic Feedback available on the iPhone 7 and above.

HapticFeedback
.notification(Notification) Notification Communicate that a task or action has succeeded, failed, or produced a warning of some kind.
.success Indicates that a task or action has completed successfully.
.warning Indicates that a task or action has produced a warning.
.failure Indicates that a task or action has failed.
.impact(Impact) Impact Indicates that an impact has occurred. For example, you might trigger impact feedback when a user interface object collides with something or snaps into place.
.light Provides a physical metaphor representing a collision between small, light user interface elements.
.medium Provides a physical metaphor representing a collision between moderately sized user interface elements.
.heavy Provides a physical metaphor representing a collision between large, heavy user interface elements.
.selection Indicates that the selection is actively changing. For example, the user feels light taps while scrolling a picker wheel.

See: Apple's Guidelines for using Haptic Feedback

.waitUntilFinished

Tells Piano to wait until the previous note is done playing before playing the next note.

.wait(TimeInterval)

Tells Piano to wait a given duration before playing the next note.

Device Capabilities

  • The iPhone 6S and 6S Plus carry the first generation of Taptic Engine which has a few "haptic" vibration patterns, which you can play with Piano using the .tapticEngine() notes.

  • The iPhone 7 and above carry the latest version of the Taptic Engine which supports the iOS 10 Haptic Feedback frameworks, allowing you to select from many more vibration types. You can play these vibrations using the .hapticFeedback() notes.

  • All versions of the iPhone can play the .vibration() notes.

Piano also includes a useful extension for UIDevice to check if the user's device has a Taptic Engine and if it supports Haptic Feedback. This extension is especially useful for creating symphonies for all devices:

if UIDevice.current.hasHapticFeedback {
    // use .hapticFeedback(HapticFeedback) notes
} else if UIDevice.current.hasTapticEngine {
    // use .tapticEngine(TapticEngine) notes
} else {
    // use .vibration(Vibration) notes
}

Note: This extension does not work on simulators, it will always return false.

Taptic Engine Guide

Apple's guide over the Haptic Feedback framework is very clear about using the Taptic Engine appropriately in order to prevent draining the user's device's battery life. Piano was built with this in mind, and handles most cases as efficiently as possible. But you can help preserve battery life and reduce latency further by calling these helper methods based on your specific needs.

1. Wake up the Taptic Engine

Piano.wakeTapticEngine()

This initializes and allocates the Haptic Feedback framework and essentially "wakes up" the Taptic Engine, as it is normally in an idle state. A good place to put this is at the begin state of a gesture or action, in anticipation of playing a .hapticFeedback() note.

2. Prepare the Taptic Engine

Piano.prepareTapticEngine()

This tells the Taptic Engine to prepare itself before creating any feedback to reduce latency when triggering feedback.

From Apple's documentation:

This is particularly important when trying to match feedback to sound or visual cues. To preserve power, the Taptic Engine stays in this prepared state for only a short period of time (on the order of seconds), or until you next trigger feedback. Think about when and where you can best prepare your generators. If you call prepare and then immediately trigger feedback, the system won’t have enough time to get the Taptic Engine into the prepared state, and you may not see a reduction in latency. On the other hand, if you call prepare too early, the Taptic Engine may become idle again before you trigger feedback.

tl;dr A good place to put this is right after calling .wakeTapticEngine(), usually at the beginning of a gesture or action, in anticipation of playing a .hapticFeedback() note.

3. Put the Taptic Engine back to Sleep

Piano.putTapticEngineToSleep()

Once we know we're done using the Taptic Engine, we can deallocate the Haptic Feedback framework, returning the Taptic Engine to its idle state. A good place to put this is at the end of a finished, cancelled, or failed gesture or action.

But you don't have to.

Piano automatically wakes and prepares the Taptic Engine when you call .play([ ... ]) if it includes a .hapticFeedback() note, and returns the Taptic Engine back to sleep when the notes are done playing.

The Example App

The example app is a great place to get started. It's designed as a playground for you to compose and test out your own symphonies of sounds and vibrations.

You can even drag and drop your own sound files into the project and tweak the code a bit to see how your own sounds can work alongside the Taptic Engine. To add your own sound file, simply drag it into Sounds.xcassets, name it accordingly, then edit the cellData property in ViewController.swift (Scroll down to case 7 in cellData, or look for "Add your own sound assets here..." in the Jump Bar using Ctrl + 6).

Documentation

Option + click on any of Piano's methods or notes for detailed documentation.

GitHub