Full Code of saoudrizwan/Piano for AI

master c508659fea5a cached
36 files
127.6 KB
34.8k tokens
1 requests
Download .txt
Repository: saoudrizwan/Piano
Branch: master
Commit: c508659fea5a
Files: 36
Total size: 127.6 KB

Directory structure:
gitextract_7isrf40a/

├── .gitignore
├── .swift-version
├── Example/
│   ├── PianoExample/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   ├── ResponsiveLabel.swift
│   │   ├── Sounds.xcassets/
│   │   │   ├── Contents.json
│   │   │   ├── heart.dataset/
│   │   │   │   └── Contents.json
│   │   │   ├── kiss.dataset/
│   │   │   │   └── Contents.json
│   │   │   └── wink.dataset/
│   │   │       └── Contents.json
│   │   └── ViewController.swift
│   └── PianoExample.xcodeproj/
│       ├── project.pbxproj
│       └── project.xcworkspace/
│           └── contents.xcworkspacedata
├── LICENSE
├── Piano.podspec
├── Piano.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   └── contents.xcworkspacedata
│   └── xcshareddata/
│       └── xcschemes/
│           └── Piano.xcscheme
├── Piano.xcworkspace/
│   └── contents.xcworkspacedata
├── README.md
├── Sources/
│   ├── Audio.swift
│   ├── HapticFeedback.swift
│   ├── Info.plist
│   ├── Note.swift
│   ├── Piano+Error.swift
│   ├── Piano.h
│   ├── Piano.swift
│   ├── SystemSound.swift
│   ├── TapticEngine.swift
│   ├── UIDevice+Extension.swift
│   └── Vibration.swift
└── Tests/
    ├── Info.plist
    └── PianoTests.swift

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
## OS X Finder
.DS_Store

## Build generated
build/
DerivedData

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xcuserstate
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

# Swift Package Manager
.build/


================================================
FILE: .swift-version
================================================
4.2


================================================
FILE: Example/PianoExample/AppDelegate.swift
================================================
//
//  AppDelegate.swift
//  PianoExample
//
//  Created by Saoud Rizwan on 9/11/17.
//  Copyright © 2017 Saoud Rizwan. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }


}



================================================
FILE: Example/PianoExample/Assets.xcassets/AppIcon.appiconset/Contents.json
================================================
{
  "images" : [
    {
      "size" : "20x20",
      "idiom" : "iphone",
      "filename" : "Icon-App-20x20@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "20x20",
      "idiom" : "iphone",
      "filename" : "Icon-App-20x20@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-App-29x29@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-App-29x29@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "29x29",
      "idiom" : "iphone",
      "filename" : "Icon-App-29x29@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "40x40",
      "idiom" : "iphone",
      "filename" : "Icon-App-40x40@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "40x40",
      "idiom" : "iphone",
      "filename" : "Icon-App-40x40@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "57x57",
      "idiom" : "iphone",
      "filename" : "Icon-App-57x57@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "57x57",
      "idiom" : "iphone",
      "filename" : "Icon-App-57x57@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "60x60",
      "idiom" : "iphone",
      "filename" : "Icon-App-60x60@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "60x60",
      "idiom" : "iphone",
      "filename" : "Icon-App-60x60@3x.png",
      "scale" : "3x"
    },
    {
      "size" : "20x20",
      "idiom" : "ipad",
      "filename" : "Icon-App-20x20@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "20x20",
      "idiom" : "ipad",
      "filename" : "Icon-App-20x20@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "29x29",
      "idiom" : "ipad",
      "filename" : "Icon-App-29x29@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "29x29",
      "idiom" : "ipad",
      "filename" : "Icon-App-29x29@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "40x40",
      "idiom" : "ipad",
      "filename" : "Icon-App-40x40@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "40x40",
      "idiom" : "ipad",
      "filename" : "Icon-App-40x40@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "50x50",
      "idiom" : "ipad",
      "filename" : "Icon-Small-50x50@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "50x50",
      "idiom" : "ipad",
      "filename" : "Icon-Small-50x50@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "72x72",
      "idiom" : "ipad",
      "filename" : "Icon-App-72x72@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "72x72",
      "idiom" : "ipad",
      "filename" : "Icon-App-72x72@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "76x76",
      "idiom" : "ipad",
      "filename" : "Icon-App-76x76@1x.png",
      "scale" : "1x"
    },
    {
      "size" : "76x76",
      "idiom" : "ipad",
      "filename" : "Icon-App-76x76@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "83.5x83.5",
      "idiom" : "ipad",
      "filename" : "Icon-App-83.5x83.5@2x.png",
      "scale" : "2x"
    },
    {
      "size" : "1024x1024",
      "idiom" : "ios-marketing",
      "filename" : "PianoAppIcon.png",
      "scale" : "1x"
    }
  ],
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: Example/PianoExample/Assets.xcassets/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: Example/PianoExample/Base.lproj/LaunchScreen.storyboard
================================================
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="EHf-IW-A2E">
            <objects>
                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="YES" id="Ze5-6b-2t3">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="53" y="375"/>
        </scene>
    </scenes>
</document>


================================================
FILE: Example/PianoExample/Base.lproj/Main.storyboard
================================================
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="RZT-lr-wh0">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
        <capability name="Safe area layout guides" minToolsVersion="9.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Navigation Controller-->
        <scene sceneID="pSI-QR-7D9">
            <objects>
                <navigationController id="RZT-lr-wh0" sceneMemberID="viewController">
                    <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="XPs-ic-HN1">
                        <rect key="frame" x="0.0" y="20" width="375" height="44"/>
                        <autoresizingMask key="autoresizingMask"/>
                    </navigationBar>
                    <connections>
                        <segue destination="BYZ-38-t0r" kind="relationship" relationship="rootViewController" id="TYX-4b-NQp"/>
                    </connections>
                </navigationController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="ucx-A2-xqN" userLabel="First Responder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="-196" y="132"/>
        </scene>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="PianoExample" customModuleProvider="target" sceneMemberID="viewController">
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" translatesAutoresizingMaskIntoConstraints="NO" id="HIm-Ye-lBa">
                                <rect key="frame" x="0.0" y="338" width="375" height="329"/>
                                <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
                                <color key="sectionIndexBackgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
                                <sections/>
                            </tableView>
                            <toolbar opaque="NO" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Z3c-N2-ow6">
                                <rect key="frame" x="0.0" y="294" width="375" height="44"/>
                                <constraints>
                                    <constraint firstAttribute="height" constant="44" id="x3O-VQ-9IA"/>
                                </constraints>
                                <items>
                                    <barButtonItem title="Item" id="efa-Dd-BH3"/>
                                </items>
                            </toolbar>
                            <label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumFontSize="5" translatesAutoresizingMaskIntoConstraints="NO" id="hyq-If-A8R" customClass="ResponsiveLabel" customModule="PianoExample" customModuleProvider="target">
                                <rect key="frame" x="20" y="84" width="335" height="190"/>
                                <constraints>
                                    <constraint firstAttribute="height" constant="190" id="zm0-Op-tO3"/>
                                </constraints>
                                <fontDescription key="fontDescription" name="Menlo-Regular" family="Menlo" pointSize="17"/>
                                <nil key="textColor"/>
                                <nil key="highlightedColor"/>
                            </label>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="hyq-If-A8R" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="20" id="09c-X9-aCL"/>
                            <constraint firstItem="HIm-Ye-lBa" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="4Vg-e3-JdB"/>
                            <constraint firstItem="Z3c-N2-ow6" firstAttribute="top" secondItem="hyq-If-A8R" secondAttribute="bottom" constant="20" id="Ijo-Sj-1Fl"/>
                            <constraint firstItem="Z3c-N2-ow6" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" id="NKi-uA-UVv"/>
                            <constraint firstItem="HIm-Ye-lBa" firstAttribute="top" secondItem="Z3c-N2-ow6" secondAttribute="bottom" id="WCL-8e-YzH"/>
                            <constraint firstItem="hyq-If-A8R" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" constant="20" id="ZR4-L6-kg2"/>
                            <constraint firstItem="Z3c-N2-ow6" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="fAd-0q-ad9"/>
                            <constraint firstItem="HIm-Ye-lBa" firstAttribute="trailing" secondItem="6Tk-OE-BBY" secondAttribute="trailing" id="iz5-dd-kzS"/>
                            <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="hyq-If-A8R" secondAttribute="trailing" constant="20" id="rFo-AP-YBA"/>
                            <constraint firstItem="HIm-Ye-lBa" firstAttribute="bottom" secondItem="8bC-Xf-vdC" secondAttribute="bottom" id="yfL-rw-1eo"/>
                        </constraints>
                        <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                    </view>
                    <navigationItem key="navigationItem" id="ru4-i9-IcL"/>
                    <connections>
                        <outlet property="label" destination="hyq-If-A8R" id="cGk-B4-Hsk"/>
                        <outlet property="tableView" destination="HIm-Ye-lBa" id="lUh-el-D4J"/>
                        <outlet property="toolBar" destination="Z3c-N2-ow6" id="iYy-Fh-iJZ"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="501.60000000000002" y="131.78410794602701"/>
        </scene>
    </scenes>
</document>


================================================
FILE: Example/PianoExample/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleDisplayName</key>
	<string>Piano</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UIRequiredDeviceCapabilities</key>
	<array>
		<string>armv7</string>
	</array>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
</dict>
</plist>


================================================
FILE: Example/PianoExample/ResponsiveLabel.swift
================================================
//
//  ResponsiveLabel.swift
//  PianoExample
//
//  Created by Saoud Rizwan on 10/1/18.
//  Copyright © 2018 Saoud Rizwan. All rights reserved.
//

import UIKit

class ResponsiveLabel: UILabel {
    override var canBecomeFirstResponder: Bool {
        return true
    }
}


================================================
FILE: Example/PianoExample/Sounds.xcassets/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  }
}

================================================
FILE: Example/PianoExample/Sounds.xcassets/heart.dataset/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  },
  "data" : [
    {
      "idiom" : "universal",
      "filename" : "Heavy Black Heart.wav"
    }
  ]
}

================================================
FILE: Example/PianoExample/Sounds.xcassets/kiss.dataset/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  },
  "data" : [
    {
      "idiom" : "universal",
      "filename" : "Kiss Mark.wav"
    }
  ]
}

================================================
FILE: Example/PianoExample/Sounds.xcassets/wink.dataset/Contents.json
================================================
{
  "info" : {
    "version" : 1,
    "author" : "xcode"
  },
  "data" : [
    {
      "idiom" : "universal",
      "filename" : "Winking Face.wav"
    }
  ]
}

================================================
FILE: Example/PianoExample/ViewController.swift
================================================
//
//  ViewController.swift
//  PianoExample
//
//  Created by Saoud Rizwan on 9/11/17.
//  Copyright © 2017 Saoud Rizwan. All rights reserved.
//

import UIKit
import Piano

class ViewController: UIViewController {

    @IBOutlet weak var label: ResponsiveLabel!
    @IBOutlet weak var toolBar: UIToolbar!
    @IBOutlet weak var tableView: UITableView!
    
    let cellData: [(title: String, rows: [(title: String, note: 🎹.Note)])] = {
        let sections = ["", "", "Vibration", "Taptic Engine", "Haptic Feedback - Notification", "Haptic Feedback - Impact", "Haptic Feedback - Selection", "Sound - Assets Example", "Sound - File Example", "Sound - URL Example", "Sound - System Predefined"]
        
        var rows = [[(title: String, note: 🎹.Note)]]()
        for i in 0..<sections.count {
            switch i {
            case 0:
                // Wait
                rows.append([
                    (".wait(text goes here)", .wait(0))
                    ])
            case 1:
                // Wait Until Finished
                rows.append([
                    (".waitUntilFinished", .waitUntilFinished)
                    ])
            case 2:
                // Vibration
                rows.append([
                    (".vibration(.default)", .vibration(.default)),
                    (".vibration(.alert)", .vibration(.alert))
                    ])
            case 3:
                // Taptic Engine
                rows.append([
                    (".tapticEngine(.peek)", .tapticEngine(.peek)),
                    (".tapticEngine(.pop)", .tapticEngine(.pop)),
                    (".tapticEngine(.cancelled)", .tapticEngine(.cancelled)),
                    (".tapticEngine(.tryAgain)", .tapticEngine(.tryAgain)),
                    (".tapticEngine(.failed)", .tapticEngine(.failed))
                    ])
            case 4:
                // Haptic Feedback - Notification
                rows.append([
                    (".hapticFeedback(.notification(.success))", .hapticFeedback(.notification(.success))),
                    (".hapticFeedback(.notification(.warning))", .hapticFeedback(.notification(.warning))),
                    (".hapticFeedback(.notification(.failure))", .hapticFeedback(.notification(.failure)))
                    ])
            case 5:
                // Haptic Feedback - Impact
                rows.append([
                    (".hapticFeedback(.impact(.light))", .hapticFeedback(.impact(.light))),
                    (".hapticFeedback(.impact(.medium))", .hapticFeedback(.impact(.medium))),
                    (".hapticFeedback(.impact(.heavy))", .hapticFeedback(.impact(.heavy)))
                    ])
            case 6:
                // Haptic Feedback - Selection
                rows.append([
                    (".hapticFeedback(.selection)", .hapticFeedback(.selection))
                    ])
            case 7:
                // Sound - Assets Example
                rows.append([
                    (".sound(.asset(name: \"heart\"))", .sound(.asset(name: "heart"))),
                    (".sound(.asset(name: \"kiss\"))", .sound(.asset(name: "kiss"))),
                    (".sound(.asset(name: \"wink\"))", .sound(.asset(name: "wink")))
                    // MARK:-
                    // MARK: Add your own sound assets here...
                    // MARK:-
                    
                    ])
            case 8:
                // Sound - File Example
                rows.append([
                    (".sound(.asset(name: \"heart\"))", .sound(.file(name: "harp", extension: "wav")))
                    // MARK:-
                    // MARK: Add your own sound files here...
                    // MARK:-
                    
                    ])
            case 9:
                // Sound - URL Example
                let joyFileUrl = Bundle.main.url(forResource: "joy", withExtension: "wav")!
                rows.append([
                    (".sound(.url(joyFileUrl))", .sound(.url(joyFileUrl)))
                    ])
            case 10:
                // Sound - System Predefined
                rows.append([
                    (".sound(.system(.newMail))", .sound(.system(.newMail))),
                    (".sound(.system(.mailSent))", .sound(.system(.mailSent))),
                    (".sound(.system(.voicemail))", .sound(.system(.voicemail)))
                    ])
                // There's too many to manually code here, so let's use some Swift black magic
                Piano.SystemSound.allCases.forEach {
                    rows[10].append((title: ".sound(.system(.\($0))", note: .sound(.system($0))))
                }
                /*
                var z = 0
                let sounds = AnyIterator {
                    let next = withUnsafeBytes(of: &z) { $0.load(as: 🎹.SystemSound.self) }
                    if next.hashValue != z { return nil }
                    z += 1
                    return next
                } as AnyIterator<🎹.SystemSound>
                for sound in sounds {
                    rows[10].append((title: ".sound(.system(.\(sound))", note: .sound(.system(sound))))
                }
                 */
                rows[10].removeSubrange(0..<3) // remove the first three we created as an example
            default: break
            }
        }
        var data = [(title: String, rows: [(title: String, note: 🎹.Note)])]()
        for i in 0..<sections.count {
            let section = sections[i]
            let rows = rows[i]
            data.append((title: section, rows: rows))
        }
        return data
    }()
    
    lazy var waitTextField: UITextField = {
        let textField = UITextField()
        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.keyboardType = .decimalPad
        textField.delegate = self
        textField.borderStyle = .none
        return textField
    }()
    
    var waitValue: TimeInterval? = nil
    
    var pianoString: String = "" {
        didSet {
            if pianoString.count == 0 {
                label.textAlignment = .center
                label.textColor = UIColor.gray
                label.text = "Add some notes to your symphony"
            } else {
                label.textAlignment = .left
                label.textColor = UIColor.black
                label.text = pianoString
            }
        }
    }
    
    var notesToPlay = [🎹.Note]()
    var notesToPlayAsStrings = [String]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "🎹 Piano"
        
        let refreshButton = UIBarButtonItem(barButtonSystemItem: .refresh, target: self, action: #selector(refreshButtonTapped))
        navigationItem.setLeftBarButton(refreshButton, animated: false)
        
        let undoButton = UIBarButtonItem(barButtonSystemItem: .undo, target: self, action: #selector(undoButtonTapped))
        navigationItem.setRightBarButton(undoButton, animated: false)
        
        pianoString = ""
        
        let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let playButton = UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(playButtonTapped))
        toolBar.setItems([space, playButton, space], animated: false)
        let shadow = UIView()
        shadow.translatesAutoresizingMaskIntoConstraints = false
        shadow.backgroundColor = UIColor.gray.withAlphaComponent(0.275)
        toolBar.addSubview(shadow)
        NSLayoutConstraint.activate([
            shadow.leadingAnchor.constraint(equalTo: toolBar.leadingAnchor),
            shadow.heightAnchor.constraint(equalToConstant: 0.75),
            shadow.trailingAnchor.constraint(equalTo: toolBar.trailingAnchor),
            shadow.bottomAnchor.constraint(equalTo: toolBar.bottomAnchor)
            ])
        let toolBarTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(toolBarTapped))
        toolBarTapGestureRecognizer.delegate = self
        toolBar.addGestureRecognizer(toolBarTapGestureRecognizer)
        
        let labelLongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(labelLongPressed))
        labelLongPressGestureRecognizer.minimumPressDuration = 0.3
        labelLongPressGestureRecognizer.delegate = self
        label.addGestureRecognizer(labelLongPressGestureRecognizer)
        
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.keyboardDismissMode = .onDrag
        
        label.lineBreakMode = .byTruncatingMiddle
        label.adjustsFontSizeToFitWidth = true
        
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    }
    
    @objc func refreshButtonTapped() {
        🎹.cancel()
        waitTextField.resignFirstResponder()
        notesToPlay.removeAll()
        notesToPlayAsStrings.removeAll()
        pianoString = ""
    }
    
    @objc func undoButtonTapped() {
        waitTextField.resignFirstResponder()
        if !notesToPlay.isEmpty {
            notesToPlay.removeLast()
        }
        if !notesToPlayAsStrings.isEmpty {
            notesToPlayAsStrings.removeLast()
        }
        setPianoStringToNotes()
    }
    
    @objc func toolBarTapped(sender: UITapGestureRecognizer) {
        tableView.setContentOffset(.zero, animated: true)
        waitTextField.resignFirstResponder()
    }
    
    @objc func labelLongPressed(sender: UILongPressGestureRecognizer) {
        guard sender.state == .began && !notesToPlay.isEmpty, let senderView = sender.view, let superView = sender.view?.superview else { return }
        senderView.becomeFirstResponder()
        let copyItem = UIMenuItem(title: "Copy", action: #selector(labelMenuCopyTapped))
        UIMenuController.shared.menuItems = [copyItem]
        UIMenuController.shared.arrowDirection = .up
        UIMenuController.shared.setTargetRect(senderView.frame, in: superView)
        UIMenuController.shared.setMenuVisible(true, animated: true)
    }
    
    @objc func labelMenuCopyTapped() {
        let text = label.text
        UIPasteboard.general.string = text
        label.resignFirstResponder()
    }
    
    @objc func playButtonTapped() {
        waitTextField.resignFirstResponder()
        if notesToPlay.count > 0 {
            label.textColor = UIColor(red: 69.0/255.0, green: 241.0/255.0, blue: 126.0/255.0, alpha: 1.0)
            🎹.play(notesToPlay) {
                self.label.textColor = UIColor.black
            }
        }
        
    }
    
    func cellTapped(indexPath: IndexPath) {
        let data = cellData[indexPath.section].rows[indexPath.row]
        switch data.note {
        case .wait, .waitUntilFinished: break
        default:
            🎹.play([data.note])
        }
    }
    
    @objc func cellAddButtonTapped(sender: UIButton) {
        waitTextField.resignFirstResponder()
        guard let cell = sender.superview as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) else { return }
        let data = cellData[indexPath.section].rows[indexPath.row]
        switch data.note {
        case .wait:
            let waitNote = 🎹.Note.wait(waitValue ?? 0)
            let waitString = ".wait(\(waitValue ?? 0.0))"
            notesToPlay.append(waitNote)
            notesToPlayAsStrings.append(waitString)
        default:
            notesToPlay.append(data.note)
            notesToPlayAsStrings.append(data.title)
        }
        setPianoStringToNotes()
    }
    
    func setPianoStringToNotes() {
        var entireCommandAsString = ""
        var numberOfLines = 0
        for i in 0..<notesToPlayAsStrings.count {
            if i == 0 {
                entireCommandAsString.append("Piano.play([\n")
                numberOfLines += 1
            }
            if i != notesToPlayAsStrings.count - 1 {
                let noteToPlayAsString = notesToPlayAsStrings[i]
                entireCommandAsString.append("    " + noteToPlayAsString + ",\n")
                numberOfLines += 1
            } else {
                let noteToPlayAsString = notesToPlayAsStrings[i]
                entireCommandAsString.append("    " + noteToPlayAsString + "\n")
                numberOfLines += 1
            }
            if i == notesToPlayAsStrings.count - 1 {
                entireCommandAsString.append("    ])")
                numberOfLines += 1
            }
        }
        pianoString = entireCommandAsString
        label.numberOfLines = numberOfLines
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func numberOfSections(in tableView: UITableView) -> Int {
        return cellData.count
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        var title = cellData[section].title
        
        let unsupported = " (UNSUPPORTED)"
        switch section {
        case 3:
            // Taptic Engine
            if !UIDevice.current.hasTapticEngine {
                title.append(unsupported)
            }
        case 4, 5, 6:
            // Haptic Feedback
            if !UIDevice.current.hasHapticFeedback {
                title.append(unsupported)
            }
        default:
            break
        }
        
        return title
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return cellData[section].rows.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cellId") ?? UITableViewCell(style: .value1, reuseIdentifier: "cellId")
        let data = cellData[indexPath.section].rows[indexPath.row]
        cell.textLabel?.text = data.title
        
        let addButton = UIButton(type: .contactAdd)
        addButton.addTarget(self, action: #selector(cellAddButtonTapped), for: .touchUpInside)
        cell.accessoryView = addButton
        
        if indexPath.section == 0 { // Wait
            cell.textLabel?.text = ""
            cell.contentView.addSubview(waitTextField)
            NSLayoutConstraint.activate([
                waitTextField.leadingAnchor.constraint(equalTo: cell.contentView.leadingAnchor, constant: 20),
                waitTextField.topAnchor.constraint(equalTo: cell.contentView.topAnchor),
                waitTextField.trailingAnchor.constraint(equalTo: cell.contentView.trailingAnchor, constant: -20),
                waitTextField.bottomAnchor.constraint(equalTo: cell.contentView.bottomAnchor)
                ])
            waitTextField.setContentHuggingPriority(UILayoutPriority.defaultLow, for: .vertical)
            
            let one = ".wait("
            let two: String = (waitValue == nil) ? "tap to input seconds" : "\(waitValue!)"
            let three = ")"
            let attributedString = NSMutableAttributedString(string: one + two + three)
            attributedString.addAttributes([.foregroundColor: UIColor.black], range: NSRange(location: 0, length: one.count))
            attributedString.addAttributes([.foregroundColor: UIColor.lightGray], range: NSRange(location: one.count, length: two.count))
            attributedString.addAttributes([.foregroundColor: UIColor.black], range: NSRange(location: one.count + two.count, length: three.count))
            waitTextField.attributedText = attributedString
        } else {
            if cell.contentView.subviews.contains(waitTextField) {
                waitTextField.removeFromSuperview()
            }
        }
        
        cell.selectionStyle = (indexPath.section == 0 || indexPath.section == 1) ? .none : .default
        
        return cell
    }
    
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 45
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        cellTapped(indexPath: indexPath)
        tableView.deselectRow(at: indexPath, animated: true)
    }
}

extension ViewController: UITextFieldDelegate {
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if textField == waitTextField {
            let text = (textField.text ?? "") as NSString
            var newString = text.replacingCharacters(in: range, with: string)
            
            newString = newString.replacingOccurrences(of: ".wait(", with: "")
            newString = newString.replacingOccurrences(of: ".wait", with: "")
            newString = newString.replacingOccurrences(of: ")", with: "")
            newString = newString.replacingOccurrences(of: "tap to input seconds", with: "")
            newString = newString.replacingOccurrences(of: " ", with: "")
            
            waitValue = TimeInterval(newString)
            
            newString = ".wait(" + newString + ")"
            let attributedString = NSMutableAttributedString(string: newString)
            attributedString.addAttributes([.foregroundColor: UIColor.black], range: NSRange(location: 0, length: newString.count))
            waitTextField.attributedText = attributedString
            
            if let newPosition = textField.position(from: textField.endOfDocument, offset: -1) {
                textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
            }
            return false
        } else {
            return true
        }
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        if textField == waitTextField {
            let string = ".wait(" + ((waitValue == nil) ? "" : "\(waitValue!)") + ")"
            let attributedString = NSMutableAttributedString(string: string)
            attributedString.addAttributes([.foregroundColor: UIColor.black], range: NSRange(location: 0, length: string.count))
            waitTextField.attributedText = attributedString
            
            if let newPosition = textField.position(from: textField.endOfDocument, offset: -1) {
                textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition)
            }
        }
    }
    
    func textFieldDidEndEditing(_ textField: UITextField) {
        if textField == waitTextField {
            let one = ".wait("
            let two: String = (waitValue == nil) ? "tap to input seconds" : "\(waitValue!)"
            let three = ")"
            let attributedString = NSMutableAttributedString(string: one + two + three)
            attributedString.addAttributes([.foregroundColor: UIColor.black], range: NSRange(location: 0, length: one.count))
            attributedString.addAttributes([.foregroundColor: UIColor.gray], range: NSRange(location: one.count, length: two.count))
            attributedString.addAttributes([.foregroundColor: UIColor.black], range: NSRange(location: one.count + two.count, length: three.count))
            waitTextField.attributedText = attributedString
        }
    }
}

extension ViewController: UIGestureRecognizerDelegate {
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let touchedView = touch.view, touchedView.isKind(of: UIControl.self) {
            return false
        } else {
            return true
        }
    }
}

extension ViewController {
    @objc private func keyboardWillShow(notification: NSNotification) {
        let waitCellIndexPath = IndexPath(row: 0, section: 0)
        if let visibleIndexPaths = tableView.indexPathsForVisibleRows, visibleIndexPaths.contains(waitCellIndexPath) {
            tableView.scrollToRow(at: waitCellIndexPath, at: UITableViewScrollPosition.middle, animated: true)
        }
    }
}


================================================
FILE: Example/PianoExample.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 48;
	objects = {

/* Begin PBXBuildFile section */
		105A40E11F675C690078BAA6 /* harp.wav in Resources */ = {isa = PBXBuildFile; fileRef = 10CCC0E51F6738010085294A /* harp.wav */; };
		105A40E21F675C690078BAA6 /* joy.wav in Resources */ = {isa = PBXBuildFile; fileRef = 105A40E01F6756970078BAA6 /* joy.wav */; };
		106A373C1F66EE0200BF5BD1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106A373B1F66EE0200BF5BD1 /* AppDelegate.swift */; };
		106A373E1F66EE0200BF5BD1 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106A373D1F66EE0200BF5BD1 /* ViewController.swift */; };
		106A37411F66EE0200BF5BD1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 106A373F1F66EE0200BF5BD1 /* Main.storyboard */; };
		106A37431F66EE0200BF5BD1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 106A37421F66EE0200BF5BD1 /* Assets.xcassets */; };
		106A37461F66EE0200BF5BD1 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 106A37441F66EE0200BF5BD1 /* LaunchScreen.storyboard */; };
		10CCC0E01F6733CD0085294A /* Piano.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 10CCC0DF1F6733C70085294A /* Piano.framework */; };
		10CCC0E11F6733CD0085294A /* Piano.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 10CCC0DF1F6733C70085294A /* Piano.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
		10CCC0E41F6737730085294A /* Sounds.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 10CCC0E31F6737730085294A /* Sounds.xcassets */; };
		DA4BC02E2162AB77006C5ADF /* ResponsiveLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4BC02D2162AB77006C5ADF /* ResponsiveLabel.swift */; };
/* End PBXBuildFile section */

/* Begin PBXCopyFilesBuildPhase section */
		10CCC0D01F6728FA0085294A /* Embed Frameworks */ = {
			isa = PBXCopyFilesBuildPhase;
			buildActionMask = 2147483647;
			dstPath = "";
			dstSubfolderSpec = 10;
			files = (
				10CCC0E11F6733CD0085294A /* Piano.framework in Embed Frameworks */,
			);
			name = "Embed Frameworks";
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
		105A40E01F6756970078BAA6 /* joy.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = joy.wav; sourceTree = "<group>"; };
		106A37381F66EE0200BF5BD1 /* PianoExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PianoExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
		106A373B1F66EE0200BF5BD1 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
		106A373D1F66EE0200BF5BD1 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
		106A37401F66EE0200BF5BD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
		106A37421F66EE0200BF5BD1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
		106A37451F66EE0200BF5BD1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
		106A37471F66EE0200BF5BD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		10CCC0DF1F6733C70085294A /* Piano.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Piano.framework; sourceTree = BUILT_PRODUCTS_DIR; };
		10CCC0E31F6737730085294A /* Sounds.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Sounds.xcassets; sourceTree = "<group>"; };
		10CCC0E51F6738010085294A /* harp.wav */ = {isa = PBXFileReference; lastKnownFileType = audio.wav; path = harp.wav; sourceTree = "<group>"; };
		DA4BC02D2162AB77006C5ADF /* ResponsiveLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponsiveLabel.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		106A37351F66EE0200BF5BD1 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				10CCC0E01F6733CD0085294A /* Piano.framework in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		106A372F1F66EE0200BF5BD1 = {
			isa = PBXGroup;
			children = (
				106A373A1F66EE0200BF5BD1 /* PianoExample */,
				106A37391F66EE0200BF5BD1 /* Products */,
				10CCC0C61F6712630085294A /* Frameworks */,
			);
			sourceTree = "<group>";
		};
		106A37391F66EE0200BF5BD1 /* Products */ = {
			isa = PBXGroup;
			children = (
				106A37381F66EE0200BF5BD1 /* PianoExample.app */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		106A373A1F66EE0200BF5BD1 /* PianoExample */ = {
			isa = PBXGroup;
			children = (
				10CCC0E21F67350C0085294A /* IGNORE */,
				10CCC0E31F6737730085294A /* Sounds.xcassets */,
				10CCC0E51F6738010085294A /* harp.wav */,
				105A40E01F6756970078BAA6 /* joy.wav */,
				106A373D1F66EE0200BF5BD1 /* ViewController.swift */,
			);
			path = PianoExample;
			sourceTree = "<group>";
		};
		10CCC0C61F6712630085294A /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				10CCC0DF1F6733C70085294A /* Piano.framework */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		10CCC0E21F67350C0085294A /* IGNORE */ = {
			isa = PBXGroup;
			children = (
				106A37421F66EE0200BF5BD1 /* Assets.xcassets */,
				106A373B1F66EE0200BF5BD1 /* AppDelegate.swift */,
				106A373F1F66EE0200BF5BD1 /* Main.storyboard */,
				106A37441F66EE0200BF5BD1 /* LaunchScreen.storyboard */,
				106A37471F66EE0200BF5BD1 /* Info.plist */,
				DA4BC02D2162AB77006C5ADF /* ResponsiveLabel.swift */,
			);
			name = IGNORE;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
		106A37371F66EE0200BF5BD1 /* PianoExample */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 106A374A1F66EE0200BF5BD1 /* Build configuration list for PBXNativeTarget "PianoExample" */;
			buildPhases = (
				106A37341F66EE0200BF5BD1 /* Sources */,
				106A37351F66EE0200BF5BD1 /* Frameworks */,
				106A37361F66EE0200BF5BD1 /* Resources */,
				10CCC0D01F6728FA0085294A /* Embed Frameworks */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = PianoExample;
			productName = PianoExample;
			productReference = 106A37381F66EE0200BF5BD1 /* PianoExample.app */;
			productType = "com.apple.product-type.application";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		106A37301F66EE0200BF5BD1 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 0900;
				LastUpgradeCheck = 0900;
				ORGANIZATIONNAME = "Saoud Rizwan";
				TargetAttributes = {
					106A37371F66EE0200BF5BD1 = {
						CreatedOnToolsVersion = 9.0;
					};
				};
			};
			buildConfigurationList = 106A37331F66EE0200BF5BD1 /* Build configuration list for PBXProject "PianoExample" */;
			compatibilityVersion = "Xcode 8.0";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 106A372F1F66EE0200BF5BD1;
			productRefGroup = 106A37391F66EE0200BF5BD1 /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				106A37371F66EE0200BF5BD1 /* PianoExample */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		106A37361F66EE0200BF5BD1 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				105A40E11F675C690078BAA6 /* harp.wav in Resources */,
				105A40E21F675C690078BAA6 /* joy.wav in Resources */,
				106A37461F66EE0200BF5BD1 /* LaunchScreen.storyboard in Resources */,
				106A37431F66EE0200BF5BD1 /* Assets.xcassets in Resources */,
				106A37411F66EE0200BF5BD1 /* Main.storyboard in Resources */,
				10CCC0E41F6737730085294A /* Sounds.xcassets in Resources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		106A37341F66EE0200BF5BD1 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				106A373E1F66EE0200BF5BD1 /* ViewController.swift in Sources */,
				106A373C1F66EE0200BF5BD1 /* AppDelegate.swift in Sources */,
				DA4BC02E2162AB77006C5ADF /* ResponsiveLabel.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXVariantGroup section */
		106A373F1F66EE0200BF5BD1 /* Main.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				106A37401F66EE0200BF5BD1 /* Base */,
			);
			name = Main.storyboard;
			sourceTree = "<group>";
		};
		106A37441F66EE0200BF5BD1 /* LaunchScreen.storyboard */ = {
			isa = PBXVariantGroup;
			children = (
				106A37451F66EE0200BF5BD1 /* Base */,
			);
			name = LaunchScreen.storyboard;
			sourceTree = "<group>";
		};
/* End PBXVariantGroup section */

/* Begin XCBuildConfiguration section */
		106A37481F66EE0200BF5BD1 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/**";
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
			};
			name = Debug;
		};
		106A37491F66EE0200BF5BD1 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/**";
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
				VALIDATE_PRODUCT = YES;
			};
			name = Release;
		};
		106A374B1F66EE0200BF5BD1 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				DEVELOPMENT_TEAM = LR7NY5NPR9;
				FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/**";
				INFOPLIST_FILE = PianoExample/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 10;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.PianoExample;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 4.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		106A374C1F66EE0200BF5BD1 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
				DEVELOPMENT_TEAM = LR7NY5NPR9;
				FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)/**";
				INFOPLIST_FILE = PianoExample/Info.plist;
				IPHONEOS_DEPLOYMENT_TARGET = 10;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.PianoExample;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SWIFT_VERSION = 4.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		106A37331F66EE0200BF5BD1 /* Build configuration list for PBXProject "PianoExample" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				106A37481F66EE0200BF5BD1 /* Debug */,
				106A37491F66EE0200BF5BD1 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		106A374A1F66EE0200BF5BD1 /* Build configuration list for PBXNativeTarget "PianoExample" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				106A374B1F66EE0200BF5BD1 /* Debug */,
				106A374C1F66EE0200BF5BD1 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = 106A37301F66EE0200BF5BD1 /* Project object */;
}


================================================
FILE: Example/PianoExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:PianoExample.xcodeproj">
   </FileRef>
</Workspace>


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2017 Saoud Rizwan <hello@saoudmr.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: Piano.podspec
================================================
Pod::Spec.new do |s|
  s.name         = "Piano"
  s.version      = "1.8"
  s.summary      = "Compose a symphony of sounds and vibrations with Taptic Engine"
  s.description  = <<-DESC
    Piano is a delightful and easy-to-use wrapper around the AVFoundation and UIHapticFeedback classes, 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.
  DESC
  s.homepage     = "https://github.com/saoudrizwan/Piano"
  s.license      = { :type => "MIT", :file => "LICENSE" }
  s.author             = { "Saoud Rizwan" => "hello@saoudmr.com" }
  s.social_media_url   = "https://twitter.com/sdrzn"
  s.platform     = :ios, "10.0"
  s.source       = { :git => "https://github.com/saoudrizwan/Piano.git", :tag => "#{s.version}" }
  s.source_files  = "Sources/**/*.{h,m,swift}"
end


================================================
FILE: Piano.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 48;
	objects = {

/* Begin PBXBuildFile section */
		106A36E81F66ECC300BF5BD1 /* Piano.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 106A36DE1F66ECC300BF5BD1 /* Piano.framework */; };
		106A36ED1F66ECC300BF5BD1 /* PianoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106A36EC1F66ECC300BF5BD1 /* PianoTests.swift */; };
		106A36EF1F66ECC300BF5BD1 /* Piano.h in Headers */ = {isa = PBXBuildFile; fileRef = 106A36E11F66ECC300BF5BD1 /* Piano.h */; settings = {ATTRIBUTES = (Public, ); }; };
		106A36F91F66ECCF00BF5BD1 /* Piano.swift in Sources */ = {isa = PBXBuildFile; fileRef = 106A36F81F66ECCF00BF5BD1 /* Piano.swift */; };
		10CCC0E71F6747FA0085294A /* SystemSound.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0E61F6747FA0085294A /* SystemSound.swift */; };
		10CCC0E91F6748340085294A /* Audio.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0E81F6748340085294A /* Audio.swift */; };
		10CCC0EB1F67484C0085294A /* Vibration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0EA1F67484C0085294A /* Vibration.swift */; };
		10CCC0ED1F6748600085294A /* TapticEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0EC1F6748600085294A /* TapticEngine.swift */; };
		10CCC0EF1F6748760085294A /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0EE1F6748760085294A /* HapticFeedback.swift */; };
		10CCC0F11F67488D0085294A /* Note.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0F01F67488D0085294A /* Note.swift */; };
		10CCC0F31F6748E20085294A /* Piano+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0F21F6748E20085294A /* Piano+Error.swift */; };
		10CCC0F51F6749FB0085294A /* UIDevice+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10CCC0F41F6749FB0085294A /* UIDevice+Extension.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
		106A36E91F66ECC300BF5BD1 /* PBXContainerItemProxy */ = {
			isa = PBXContainerItemProxy;
			containerPortal = 106A36D51F66ECC300BF5BD1 /* Project object */;
			proxyType = 1;
			remoteGlobalIDString = 106A36DD1F66ECC300BF5BD1;
			remoteInfo = Piano;
		};
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
		106A36DE1F66ECC300BF5BD1 /* Piano.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Piano.framework; sourceTree = BUILT_PRODUCTS_DIR; };
		106A36E11F66ECC300BF5BD1 /* Piano.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Piano.h; sourceTree = "<group>"; };
		106A36E21F66ECC300BF5BD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		106A36E71F66ECC300BF5BD1 /* PianoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PianoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
		106A36EC1F66ECC300BF5BD1 /* PianoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PianoTests.swift; sourceTree = "<group>"; };
		106A36EE1F66ECC300BF5BD1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
		106A36F81F66ECCF00BF5BD1 /* Piano.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Piano.swift; sourceTree = "<group>"; };
		10CCC0E61F6747FA0085294A /* SystemSound.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemSound.swift; sourceTree = "<group>"; };
		10CCC0E81F6748340085294A /* Audio.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Audio.swift; sourceTree = "<group>"; };
		10CCC0EA1F67484C0085294A /* Vibration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Vibration.swift; sourceTree = "<group>"; };
		10CCC0EC1F6748600085294A /* TapticEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapticEngine.swift; sourceTree = "<group>"; };
		10CCC0EE1F6748760085294A /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = "<group>"; };
		10CCC0F01F67488D0085294A /* Note.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Note.swift; sourceTree = "<group>"; };
		10CCC0F21F6748E20085294A /* Piano+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Piano+Error.swift"; sourceTree = "<group>"; };
		10CCC0F41F6749FB0085294A /* UIDevice+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIDevice+Extension.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		106A36DA1F66ECC300BF5BD1 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		106A36E41F66ECC300BF5BD1 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				106A36E81F66ECC300BF5BD1 /* Piano.framework in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		106A36D41F66ECC300BF5BD1 = {
			isa = PBXGroup;
			children = (
				106A36E01F66ECC300BF5BD1 /* Sources */,
				106A36EB1F66ECC300BF5BD1 /* Tests */,
				106A36DF1F66ECC300BF5BD1 /* Products */,
			);
			sourceTree = "<group>";
		};
		106A36DF1F66ECC300BF5BD1 /* Products */ = {
			isa = PBXGroup;
			children = (
				106A36DE1F66ECC300BF5BD1 /* Piano.framework */,
				106A36E71F66ECC300BF5BD1 /* PianoTests.xctest */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		106A36E01F66ECC300BF5BD1 /* Sources */ = {
			isa = PBXGroup;
			children = (
				106A36E21F66ECC300BF5BD1 /* Info.plist */,
				106A36E11F66ECC300BF5BD1 /* Piano.h */,
				106A36F81F66ECCF00BF5BD1 /* Piano.swift */,
				10CCC0F21F6748E20085294A /* Piano+Error.swift */,
				10CCC0F01F67488D0085294A /* Note.swift */,
				10CCC0E61F6747FA0085294A /* SystemSound.swift */,
				10CCC0E81F6748340085294A /* Audio.swift */,
				10CCC0EA1F67484C0085294A /* Vibration.swift */,
				10CCC0EC1F6748600085294A /* TapticEngine.swift */,
				10CCC0EE1F6748760085294A /* HapticFeedback.swift */,
				10CCC0F41F6749FB0085294A /* UIDevice+Extension.swift */,
			);
			path = Sources;
			sourceTree = "<group>";
		};
		106A36EB1F66ECC300BF5BD1 /* Tests */ = {
			isa = PBXGroup;
			children = (
				106A36EC1F66ECC300BF5BD1 /* PianoTests.swift */,
				106A36EE1F66ECC300BF5BD1 /* Info.plist */,
			);
			path = Tests;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
		106A36DB1F66ECC300BF5BD1 /* Headers */ = {
			isa = PBXHeadersBuildPhase;
			buildActionMask = 2147483647;
			files = (
				106A36EF1F66ECC300BF5BD1 /* Piano.h in Headers */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXHeadersBuildPhase section */

/* Begin PBXNativeTarget section */
		106A36DD1F66ECC300BF5BD1 /* Piano */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 106A36F21F66ECC300BF5BD1 /* Build configuration list for PBXNativeTarget "Piano" */;
			buildPhases = (
				106A36D91F66ECC300BF5BD1 /* Sources */,
				106A36DA1F66ECC300BF5BD1 /* Frameworks */,
				106A36DB1F66ECC300BF5BD1 /* Headers */,
				106A36DC1F66ECC300BF5BD1 /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = Piano;
			productName = Piano;
			productReference = 106A36DE1F66ECC300BF5BD1 /* Piano.framework */;
			productType = "com.apple.product-type.framework";
		};
		106A36E61F66ECC300BF5BD1 /* PianoTests */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 106A36F51F66ECC300BF5BD1 /* Build configuration list for PBXNativeTarget "PianoTests" */;
			buildPhases = (
				106A36E31F66ECC300BF5BD1 /* Sources */,
				106A36E41F66ECC300BF5BD1 /* Frameworks */,
				106A36E51F66ECC300BF5BD1 /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
				106A36EA1F66ECC300BF5BD1 /* PBXTargetDependency */,
			);
			name = PianoTests;
			productName = PianoTests;
			productReference = 106A36E71F66ECC300BF5BD1 /* PianoTests.xctest */;
			productType = "com.apple.product-type.bundle.unit-test";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		106A36D51F66ECC300BF5BD1 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastSwiftUpdateCheck = 0900;
				LastUpgradeCheck = 0900;
				ORGANIZATIONNAME = "Saoud Rizwan";
				TargetAttributes = {
					106A36DD1F66ECC300BF5BD1 = {
						CreatedOnToolsVersion = 9.0;
						LastSwiftMigration = 0900;
					};
					106A36E61F66ECC300BF5BD1 = {
						CreatedOnToolsVersion = 9.0;
						ProvisioningStyle = Manual;
					};
				};
			};
			buildConfigurationList = 106A36D81F66ECC300BF5BD1 /* Build configuration list for PBXProject "Piano" */;
			compatibilityVersion = "Xcode 8.0";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
			);
			mainGroup = 106A36D41F66ECC300BF5BD1;
			productRefGroup = 106A36DF1F66ECC300BF5BD1 /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				106A36DD1F66ECC300BF5BD1 /* Piano */,
				106A36E61F66ECC300BF5BD1 /* PianoTests */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		106A36DC1F66ECC300BF5BD1 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		106A36E51F66ECC300BF5BD1 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		106A36D91F66ECC300BF5BD1 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				10CCC0F31F6748E20085294A /* Piano+Error.swift in Sources */,
				10CCC0EB1F67484C0085294A /* Vibration.swift in Sources */,
				10CCC0E91F6748340085294A /* Audio.swift in Sources */,
				10CCC0F11F67488D0085294A /* Note.swift in Sources */,
				10CCC0F51F6749FB0085294A /* UIDevice+Extension.swift in Sources */,
				10CCC0ED1F6748600085294A /* TapticEngine.swift in Sources */,
				10CCC0E71F6747FA0085294A /* SystemSound.swift in Sources */,
				106A36F91F66ECCF00BF5BD1 /* Piano.swift in Sources */,
				10CCC0EF1F6748760085294A /* HapticFeedback.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
		106A36E31F66ECC300BF5BD1 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				106A36ED1F66ECC300BF5BD1 /* PianoTests.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
		106A36EA1F66ECC300BF5BD1 /* PBXTargetDependency */ = {
			isa = PBXTargetDependency;
			target = 106A36DD1F66ECC300BF5BD1 /* Piano */;
			targetProxy = 106A36E91F66ECC300BF5BD1 /* PBXContainerItemProxy */;
		};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
		106A36F01F66ECC300BF5BD1 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				CURRENT_PROJECT_VERSION = 1;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				VERSIONING_SYSTEM = "apple-generic";
				VERSION_INFO_PREFIX = "";
			};
			name = Debug;
		};
		106A36F11F66ECC300BF5BD1 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				CODE_SIGN_IDENTITY = "iPhone Developer";
				COPY_PHASE_STRIP = NO;
				CURRENT_PROJECT_VERSION = 1;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu11;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
				VALIDATE_PRODUCT = YES;
				VERSIONING_SYSTEM = "apple-generic";
				VERSION_INFO_PREFIX = "";
			};
			name = Release;
		};
		106A36F31F66ECC300BF5BD1 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_IDENTITY = "";
				DEFINES_MODULE = YES;
				DEVELOPMENT_TEAM = "";
				DYLIB_COMPATIBILITY_VERSION = 1;
				DYLIB_CURRENT_VERSION = 1;
				DYLIB_INSTALL_NAME_BASE = "@rpath";
				INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.Piano;
				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
				SKIP_INSTALL = YES;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 4.2;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		106A36F41F66ECC300BF5BD1 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CLANG_ENABLE_MODULES = YES;
				CODE_SIGN_IDENTITY = "";
				DEFINES_MODULE = YES;
				DEVELOPMENT_TEAM = "";
				DYLIB_COMPATIBILITY_VERSION = 1;
				DYLIB_CURRENT_VERSION = 1;
				DYLIB_INSTALL_NAME_BASE = "@rpath";
				INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.Piano;
				PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
				SKIP_INSTALL = YES;
				SWIFT_VERSION = 4.2;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
		106A36F61F66ECC300BF5BD1 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				CODE_SIGN_STYLE = Manual;
				DEVELOPMENT_TEAM = 5M795QY47C;
				INFOPLIST_FILE = Tests/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.PianoTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				SWIFT_VERSION = 4.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Debug;
		};
		106A36F71F66ECC300BF5BD1 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
				CODE_SIGN_STYLE = Manual;
				DEVELOPMENT_TEAM = 5M795QY47C;
				INFOPLIST_FILE = Tests/Info.plist;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = saoudrizwan.PianoTests;
				PRODUCT_NAME = "$(TARGET_NAME)";
				PROVISIONING_PROFILE_SPECIFIER = "";
				SWIFT_VERSION = 4.0;
				TARGETED_DEVICE_FAMILY = "1,2";
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		106A36D81F66ECC300BF5BD1 /* Build configuration list for PBXProject "Piano" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				106A36F01F66ECC300BF5BD1 /* Debug */,
				106A36F11F66ECC300BF5BD1 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		106A36F21F66ECC300BF5BD1 /* Build configuration list for PBXNativeTarget "Piano" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				106A36F31F66ECC300BF5BD1 /* Debug */,
				106A36F41F66ECC300BF5BD1 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		106A36F51F66ECC300BF5BD1 /* Build configuration list for PBXNativeTarget "PianoTests" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				106A36F61F66ECC300BF5BD1 /* Debug */,
				106A36F71F66ECC300BF5BD1 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = 106A36D51F66ECC300BF5BD1 /* Project object */;
}


================================================
FILE: Piano.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:Piano.xcodeproj">
   </FileRef>
</Workspace>


================================================
FILE: Piano.xcodeproj/xcshareddata/xcschemes/Piano.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "0900"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "106A36DD1F66ECC300BF5BD1"
               BuildableName = "Piano.framework"
               BlueprintName = "Piano"
               ReferencedContainer = "container:Piano.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      language = ""
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
      <AdditionalOptions>
      </AdditionalOptions>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      language = ""
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "106A36DD1F66ECC300BF5BD1"
            BuildableName = "Piano.framework"
            BlueprintName = "Piano"
            ReferencedContainer = "container:Piano.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
      <AdditionalOptions>
      </AdditionalOptions>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "106A36DD1F66ECC300BF5BD1"
            BuildableName = "Piano.framework"
            BlueprintName = "Piano"
            ReferencedContainer = "container:Piano.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>


================================================
FILE: Piano.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "group:Example/PianoExample.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:Piano.xcodeproj">
   </FileRef>
   <FileRef
      location = "group:README.md">
   </FileRef>
   <FileRef
      location = "group:LICENSE">
   </FileRef>
   <FileRef
      location = "group:Piano.podspec">
   </FileRef>
</Workspace>


================================================
FILE: README.md
================================================
<p align="center">
    <img src="https://user-images.githubusercontent.com/7799382/30356431-dbba9920-97ed-11e7-8f2b-a5b5ba0e7682.png" alt="Piano" />
</p>

<p align="center">
    <img src="https://user-images.githubusercontent.com/7799382/30309920-bcdb85ec-9742-11e7-96fc-af8155f4712d.png" alt="Platform: iOS 10.0+" />
    <a href="https://developer.apple.com/swift" target="_blank"><img src="https://user-images.githubusercontent.com/7799382/30309908-ace5d886-9742-11e7-85ea-8d4e5f2af2ac.png" alt="Language: Swift 4" /></a>
    <a href="https://cocoapods.org/pods/Piano" target="_blank"><img src="https://user-images.githubusercontent.com/7799382/33073452-cd78293e-ce77-11e7-8b39-8a1565616814.png" alt="CocoaPods compatible" /></a>
    <a href="https://github.com/Carthage/Carthage" target="_blank"><img src="https://user-images.githubusercontent.com/7799382/30309900-9fc15d2e-9742-11e7-91fd-31bb1226db90.png" alt="Carthage compatible" /></a>
    <img src="https://user-images.githubusercontent.com/7799382/30309910-adef2b38-9742-11e7-8140-d05534dd92a5.png" alt="License: MIT" />
</p>

<p align="center">
    <a href="#installation">Installation</a>
  • <a href="#usage">Usage</a>
  • <a href="#documentation">Documentation</a>
  • <a href="#why-i-built-piano">Why I Built Piano</a>
  • <a href="#license">License</a>
  • <a href="#contribute">Contribute</a>
  • <a href="#questions">Questions?</a>
  • <a href="#credits">Credits</a>
</p>

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.2** projects.

## Installation

* Installation for <a href="https://guides.cocoapods.org/using/using-cocoapods.html" target="_blank">CocoaPods</a>:

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

    pod 'Piano', '~> 1.8'

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

* Installation for <a href="https://github.com/Carthage/Carthage" target="_blank">Carthage</a>:

 ```ruby
 github "saoudrizwan/Piano"
 ```
 *(make sure Xcode 10 is [set as your system's default Xcode](https://stackoverflow.com/a/28901378/3502608) 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.
```swift
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:
```swift
🎹.play([
    .sound(.asset(name: "acapella"))
    ])
```
Optionally add a completion block to be called when all the notes are finished playing:
```swift
🎹.play([
    .sound(.asset(name: "acapella"))
]) {
    // ...
}
```
Or cancel the currently playing symphony:
```swift
🎹.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](https://github.com/saoudrizwan/Piano/blob/master/Sources/SystemSound.swift). |

#### `.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.|

<sub>See: [Apple's Guidelines for using Haptic Feedback](https://developer.apple.com/ios/human-interface-guidelines/user-interaction/feedback/)</sub>

#### `.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:
```swift
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](https://developer.apple.com/documentation/uikit/uifeedbackgenerator) 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
```swift
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

```swift
Piano.prepareTapticEngine()
```
This tells the Taptic Engine to prepare itself before creating any feedback to reduce latency when triggering feedback.

From Apple's [documentation](https://developer.apple.com/documentation/uikit/uifeedbackgenerator):
> 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
```swift
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](https://github.com/saoudrizwan/Piano/tree/master/Example) 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.

<p align="center">
<img src="https://user-images.githubusercontent.com/7799382/30370416-613f985a-982c-11e7-8646-33f1efb55d90.png" alt="Piano" width="300" height="500" />
</p>

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.
<img src="https://user-images.githubusercontent.com/7799382/30358465-97784ee0-97f9-11e7-9f12-75fa041cf556.png" alt="documentation">


## Why I Built Piano

With the new iPhone 8 and iPhone X, we are going to see many new Augmented Reality apps, and one of the keypoints in the [Human Interface Guidelines for AR](https://developer.apple.com/ios/human-interface-guidelines/technologies/augmented-reality/) is to not clutter the AR view, allowing as much content from the augmented reality to be displayed as possible. Besides AR, Apple has spent tremendous time and manpower giving the iPhone an interface beyond our vision with the Taptic Engine and Siri. Apple even had a [session during WWDC 2017](https://developer.apple.com/videos/play/wwdc2017/803/) talking about the importance of sound design and the impact it can have on a user experience. It's obvious that the future of technology is not visual interfaces, but augmenting our connection with the real world. By using our physical, auditory, and most importantly visual senses, we can see the world in a whole new light. That's why I built Piano and [ARLogger](https://github.com/saoudrizwan/ARLogger), frameworks I hope will help developers create immersive and uncluttered interfaces, while keeping the user aware of the technology's state and purpose. If you'd like my help on an AR project, or just want to chat about the future of technology, don't hesitate to reach out to me on Twitter [@sdrzn](http://twitter.com/sdrzn).

## License

Piano uses the MIT license. Please file an issue if you have any questions or if you'd like to share how you're using Piano.

## Contribute

Please feel free to create issues for feature requests or send pull requests of any additions you think would complement Piano and its philosophy.

## Questions?

Contact me by email <a href="mailto:hello@saoudmr.com">hello@saoudmr.com</a>, or by twitter <a href="https://twitter.com/sdrzn" target="_blank">@sdrzn</a>. Please create an <a href="https://github.com/saoudrizwan/Piano/issues">issue</a> if you come across a bug or would like a feature to be added.

## Credits

* Example app sound files from [Icons 8 UI Sounds](https://icons8.com/sounds)
* Music notes in README header image from [LSE Design on the Noun Project](https://thenounproject.com/LSEdesigns/collection/music-notes/)


================================================
FILE: Sources/Audio.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

@available(iOS 10.0, *)
extension Piano {
    /// Audio file to play
    public enum Audio {
        /// Name of asset in any .xcassets catalogs
        case asset(name: String)
        
        /// Searches main bundle for file with given name and extension
        case file(name: String, extension: String)
        
        /// URL of audio file
        case url(URL)
        
        /// Predefined system sound included in all iPhones
        case system(SystemSound)
    }
}


================================================
FILE: Sources/HapticFeedback.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

@available(iOS 10.0, *)
extension Piano {
    /// Second Generation Taptic Engine vibration options
    public enum HapticFeedback {
        /// Use notification feedback to communicate that a task or action has succeeded, failed, or produced a warning of some kind.
        case notification(Notification)
        public enum Notification {
            /// Indicates that a task or action, such as depositing a check or unlocking a vehicle, has completed.
            case success
            
            /// Indicates that a task or action, such as depositing a check or unlocking a vehicle, has produced a warning of some kind.
            case warning
            
            /// Indicates that a task or action, such as depositing a check or unlocking a vehicle, has failed.
            case failure
        }
        
        /// Use impact feedback generators to indicate that an impact has occurred. For example, you might trigger impact feedback when a user interface object collides with something or snaps into place.
        case impact(Impact)
        public enum Impact {
            /// Provides a physical metaphor representing a collision between small, light user interface elements. For example, the user might feel a thud when a view slides into place or two objects collide.
            case light
            
            /// Provides a physical metaphor representing a collision between moderately sized user interface elements. For example, the user might feel a thud when a view slides into place or two objects collide.
            case medium
            
            /// Provides a physical metaphor representing a collision between large, heavy user interface elements. For example, the user might feel a thud when a view slides into place or two objects collide.
            case heavy
        }
        
        /// Indicates that the selection is actively changing. For example, the user feels light taps while scrolling a picker wheel. This feedback is intended for communicating movement through a series of discrete values, not making or confirming a selection.
        case selection
    }
}



================================================
FILE: Sources/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>FMWK</string>
	<key>CFBundleShortVersionString</key>
	<string>1.8</string>
	<key>CFBundleVersion</key>
	<string>$(CURRENT_PROJECT_VERSION)</string>
	<key>NSPrincipalClass</key>
	<string></string>
</dict>
</plist>


================================================
FILE: Sources/Note.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

@available(iOS 10.0, *)
extension Piano {
    /// Sound, feedback, vibration, or pause for Piano to play
    public enum Note {
        /// Audio file to play
        case sound(Audio)
        
        /// Standard vibrations available on all models of the iPhone
        case vibration(Vibration)
        
        /// First generation Taptic Engine vibrations
        case tapticEngine(TapticEngine)
        
        /// Second Generation Taptic Engine vibrations
        case hapticFeedback(HapticFeedback)
        
        /// Tells Piano to wait until the previous note is done playing before playing the next note
        case waitUntilFinished
        
        /// Tells Piano to wait a given duration before playing the next note
        case wait(TimeInterval)
    }
}


================================================
FILE: Sources/Piano+Error.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

@available(iOS 10.0, *)
extension Piano {
    /// Possible errors when trying to play notes
    public enum PianoError: Error {
        case notFound(String)
        case couldNotPlay(String)
    }
    
    /// Currently, printing the errors in console is the most friendly way to handle them
    func handle(error: Error) {
        if let error = error as? PianoError {
            switch error {
            case .notFound(let name):
                print("🎹 Piano could not find \(name)!")
            case .couldNotPlay(let name):
                print("🎹 Piano could not play \(name)!")
            }
        } else {
            let error = error as NSError
            print("""
                🎹 Piano encountered an error!
                Domain: \(error.domain)
                Code: \(error.code)
                Description: \(error.localizedDescription)
                Failure Reason: \(error.localizedFailureReason ?? "")
                Suggestions: \(error.localizedRecoverySuggestion ?? "")
                """)
        }
    }
}


================================================
FILE: Sources/Piano.h
================================================
//
//  Piano.h
//  Piano
//
//  Created by Saoud Rizwan on 9/11/17.
//  Copyright © 2017 Saoud Rizwan. All rights reserved.
//

#import <UIKit/UIKit.h>

//! Project version number for Piano.
FOUNDATION_EXPORT double PianoVersionNumber;

//! Project version string for Piano.
FOUNDATION_EXPORT const unsigned char PianoVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <Piano/PublicHeader.h>




================================================
FILE: Sources/Piano.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation
import AudioToolbox.AudioServices
import AVFoundation

@available(iOS 10.0, *)
public typealias 🎹 = Piano

/// Piano
///
/// Compose a symphony of sounds and vibrations using Taptic Engine
@available(iOS 10.0, *)
public class Piano {
    
    /// Internal instance of Piano to manage shared feedback generators and symphony trackers
    private static let `default` = Piano()
    
    /// Allocatable/deallocatable tuple of UIFeedbackGenerators (Apple recommended)
    private var feedbackGenerator: (notification: UINotificationFeedbackGenerator?,
        impact: (light: UIImpactFeedbackGenerator?,
        medium: UIImpactFeedbackGenerator?,
        heavy: UIImpactFeedbackGenerator?),
        selection: UISelectionFeedbackGenerator?) = (nil, (nil, nil, nil), nil)
    
    private var player: AVAudioPlayer?
    
    /// Keeps track of multiple symphonies, preventing multiple symphonies from being played at once
    private var symphonyCounter = 0
    
    /// Holds all the scheduled Timers with music
    private var timers = [Timer]()
    
    private init() { }
    
    /// Wakes the Taptic Engine up from an idle state
    public static func wakeTapticEngine() {
        if Piano.default.feedbackGenerator.notification == nil {
            Piano.default.feedbackGenerator = (notification: UINotificationFeedbackGenerator(),
                                               impact: (light: UIImpactFeedbackGenerator(style: .light),
                                                        medium: UIImpactFeedbackGenerator(style: .medium),
                                                        heavy: UIImpactFeedbackGenerator(style: .heavy)),
                                               selection: UISelectionFeedbackGenerator())
        }
    }
    
    /// This tells the Taptic Engine to prepare itself before creating any feedback to reduce latency when triggering feedback. You can call this as many times as you want, preferrably right before playing a .hapticFeedback note.
    ///
    /// Apple docs:
    /// When you call this method, the generator is placed into a prepared state for a short period of time. While the generator is prepared, you can trigger feedback with lower latency.
    /// Think about when you can best prepare your generators. Call prepare() before the event that triggers feedback. The system needs time to prepare the Taptic Engine for minimal latency. Calling prepare() and then immediately triggering feedback (without any time in between) does not improve latency.
    /// To conserve power, the Taptic Engine returns to an idle state after any of the following events:
    /// - You trigger feedback on the generator.
    /// - A short period of time passes (typically seconds).
    /// - The generator is deallocated.
    ///
    /// After feedback is triggered, the Taptic Engine returns to its idle state. If you might trigger additional feedback within the next few seconds, immediately call prepare() to keep the Taptic Engine in the prepared state.
    /// You can also extend the prepared state by repeatedly calling the prepare() method. However, if you continue calling prepare() without ever triggering feedback, the system may eventually place the Taptic Engine back in an idle state and ignore any further prepare() calls until after you trigger feedback at least once.
    public static func prepareTapticEngine() {
        if Piano.default.feedbackGenerator.notification == nil {
            Piano.wakeTapticEngine()
        }
        Piano.default.feedbackGenerator.selection?.prepare()
        Piano.default.feedbackGenerator.notification?.prepare()
        Piano.default.feedbackGenerator.impact.light?.prepare()
        Piano.default.feedbackGenerator.impact.medium?.prepare()
        Piano.default.feedbackGenerator.impact.heavy?.prepare()
    }
    
    /// Returns the Taptic Engine to an idle state
    public static func putTapticEngineToSleep() {
        Piano.default.feedbackGenerator = (nil, (nil, nil, nil), nil)
    }
    
    /// Plays the audio asset with the given name
    ///
    /// - Parameters:
    ///   - assetName: name of asset as per in its respective .xcassets catalog
    ///   - completion: completion handler
    private func playAudio(from assetName: String, completion: (() -> Void)?) {
        guard let asset = NSDataAsset(name: assetName) else {
            handle(error: PianoError.notFound(assetName))
            completion?()
            return
        }
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
            try AVAudioSession.sharedInstance().setActive(true)
            player = try AVAudioPlayer(data: asset.data, fileTypeHint: nil)
            if let player = player {
                player.play()
                DispatchQueue.main.asyncAfter(deadline: .now() + player.duration, execute: {
                    completion?()
                })
            } else {
                handle(error: PianoError.couldNotPlay(assetName))
                completion?()
            }
        } catch {
            handle(error: error)
            completion?()
        }
    }
    
    /// Plays the audio file with the given name and extension
    ///
    /// - Parameters:
    ///   - file: name of file (Sound.mp4 -> ("Sound", "mp4")
    ///   - completion: completion handler
    private func playAudio(from file: (name: String, extension: String), completion: (() -> Void)?) {
        guard let url = Bundle.main.url(forResource: file.name, withExtension: file.extension) else {
            handle(error: PianoError.notFound("\(file.name + "." + file.extension)"))
            completion?()
            return
        }
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
            try AVAudioSession.sharedInstance().setActive(true)
            player = try AVAudioPlayer(contentsOf: url)
            if let player = player {
                player.play()
                DispatchQueue.main.asyncAfter(deadline: .now() + player.duration, execute: {
                    completion?()
                })
            } else {
                handle(error: PianoError.couldNotPlay("\(file.name + "." + file.extension)"))
                completion?()
            }
        } catch {
            handle(error: error)
            completion?()
        }
    }
    
    /// Plays the audio from the specified URL
    ///
    /// - Parameters:
    ///   - url: file URL of audio file
    ///   - completion: completion handler
    private func playAudio(from url: URL, completion: (() -> Void)?) {
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
            try AVAudioSession.sharedInstance().setActive(true)
            player = try AVAudioPlayer(contentsOf: url)
            if let player = player {
                player.play()
                DispatchQueue.main.asyncAfter(deadline: .now() + player.duration, execute: {
                    completion?()
                })
            } else {
                handle(error: PianoError.couldNotPlay(url.absoluteString))
                completion?()
            }
        } catch {
            handle(error: error)
            completion?()
        }
    }
    
    /// Plays system sound using Audio Services
    ///
    /// - Parameters:
    ///   - soundId: System Sound ID of sound
    ///   - completion: completion handler
    private func playSystemSound(with soundId: Int, completion: (() -> Void)?) {
        AudioServicesPlaySystemSoundWithCompletion(SystemSoundID(soundId)) {
            DispatchQueue.main.async {
                completion?()
            }
        }
    }
    
    /// Plays the specified haptic feedback, calling the specified completion handler after a time manually calculated from Apple's website
    ///
    /// - Parameters:
    ///   - feedback: type of feedback to generate
    ///   - completion: completion handler
    private func playHapticFeedback(_ feedback: HapticFeedback, completion: (() -> Void)?) {
        let duration: TimeInterval // value is calculated from https://developer.apple.com/ios/human-interface-guidelines/interaction/feedback/
        switch feedback {
        case .notification(let notification):
            switch notification {
            case .success:
                Piano.default.feedbackGenerator.notification?.notificationOccurred(.success)
                duration = 0.2
            case .warning:
                Piano.default.feedbackGenerator.notification?.notificationOccurred(.warning)
                duration = 0.25
            case .failure:
                Piano.default.feedbackGenerator.notification?.notificationOccurred(.error)
                duration = 0.5
            }
        case .impact(let impact):
            switch impact {
            case .light:
                Piano.default.feedbackGenerator.impact.light?.impactOccurred()
            case .medium:
                Piano.default.feedbackGenerator.impact.medium?.impactOccurred()
            case .heavy:
                Piano.default.feedbackGenerator.impact.heavy?.impactOccurred()
            }
            duration = 0.1
        case .selection:
            Piano.default.feedbackGenerator.selection?.selectionChanged()
            duration = 0.05
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: {
            completion?()
        })
    }
    
    /// Cancels the currently playing symphony
    public static func cancel() {
        for timer in Piano.default.timers {
            timer.invalidate()
        }
        Piano.default.timers.removeAll()
    }
    
    /// Play a symphony of notes
    ///
    /// Note: This method automatically cancels any previously playing symphonies
    public static func play(_ notes: [Note], completion: (() -> Void)? = nil) {
        cancel()
        Piano.default.symphonyCounter += 1
        var pauseDurationBeforeNextNote: TimeInterval = 0
        let notes = Piano.default.removeUnnecessaryNotes(from: notes)
        var completion = completion
        if notes.contains(where: { (note) -> Bool in
            switch note {
            case .hapticFeedback: return true
            default: return false
            }
        }) {
            prepareTapticEngine()
            if let definedCompletion = completion {
                let newCompletion: (() -> Void) = {
                    definedCompletion()
                    putTapticEngineToSleep()
                }
                completion = newCompletion
            } else {
                completion = {
                    putTapticEngineToSleep()
                }
            }
        }
        notesLoop: for i in 0..<notes.count {
            let note = notes[i]
            var music: (() -> Void)? = nil
            var iterationCompletion: (() -> Void)? = nil
            if (i < notes.count - 2) {
                let nextNote = notes[i + 1]
                switch nextNote {
                case .waitUntilFinished:
                    let afterNextNoteIndex = i + 2
                    let finalNoteIndex = notes.count - 1
                    let restOfNotes = Array(notes[afterNextNoteIndex...finalNoteIndex])
                    let capturedCounter = Piano.default.symphonyCounter
                    iterationCompletion = {
                        if Piano.default.symphonyCounter == capturedCounter {
                            play(restOfNotes, completion: completion)
                        }
                    }
                default: break
                }
            } else if (i < notes.count - 1) {
                let nextNote = notes[i + 1]
                switch nextNote {
                case .waitUntilFinished:
                    iterationCompletion = completion
                default: break
                }
            } else if i == notes.count - 1 {
                iterationCompletion = completion
            }
            switch note {
            case .sound(let audio):
                switch audio {
                case .asset(let name):
                    music = { Piano.default.playAudio(from: name, completion: iterationCompletion) }
                case .file(let name, let type):
                    music = { Piano.default.playAudio(from: (name, type), completion: iterationCompletion) }
                case .url(let url):
                    music = { Piano.default.playAudio(from: url, completion: iterationCompletion) }
                case .system(let sound):
                    music = { Piano.default.playSystemSound(with: sound.rawValue, completion: iterationCompletion) }
                }
            case .vibration(let vibration):
                music = { Piano.default.playSystemSound(with: vibration.rawValue, completion: iterationCompletion) }
            case .tapticEngine(let engine):
                music = { Piano.default.playSystemSound(with: engine.rawValue, completion: iterationCompletion) }
            case .hapticFeedback(let feedback):
                music = { Piano.default.playHapticFeedback(feedback, completion: iterationCompletion) }
            case .waitUntilFinished:
                if i != 0 {
                    break notesLoop
                }
            case .wait(let interval):
                pauseDurationBeforeNextNote += interval
                if i == notes.count - 1 {
                    music = { iterationCompletion?() }
                }
            }
            if let music = music {
                let timer = Timer(timeInterval: pauseDurationBeforeNextNote, repeats: false) { (_) in
                    music()
                }
                RunLoop.main.add(timer, forMode: .common)
                Piano.default.timers.append(timer)
            }
        }
        if notes.count == 0 {
            completion?()
        }
    }
    
    /// Helper method for .play() to remove unnecessary .waitUntileFinisheds
    private func removeUnnecessaryNotes(from notes: [Note]) -> [Note] {
        var results = [Note]()
        for note in notes {
            if results.count == 0 {
                results.append(note)
            } else if let last = results.last {
                switch note {
                case .waitUntilFinished:
                    switch last {
                    case .waitUntilFinished: break
                    default: results.append(note)
                    }
                default: results.append(note)
                }
            }
        }
        if results.count == 1 {
            let onlyNote = results[0]
            switch onlyNote {
            case .waitUntilFinished: return []
            default: break
            }
        } else {
            var removedFirstWaits = false
            var removedLastWaits = false
            while !removedFirstWaits || !removedLastWaits {
                switch results.first! {
                case .waitUntilFinished: results.removeFirst()
                default: removedFirstWaits = true
                }
                switch results.last! {
                case .waitUntilFinished: results.removeLast()
                default: removedLastWaits = true
                }
            }
        }
        return results
    }
}


================================================
FILE: Sources/SystemSound.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

@available(iOS 10.0, *)
extension Piano {
    /// Default system sounds predefined and available on all iPhones
    /// Source: http://iphonedevwiki.net/index.php/AudioServices
    public enum SystemSound: Int, CaseIterable {
        case newMail = 1000
        case mailSent = 1001
        case voicemail = 1002
        case receivedMessage = 1003
        case sentMessage = 1004
        case alarm = 1005
        case lowPower = 1006
        case smsReceived1 = 1007
        case smsReceived2 = 1008
        case smsReceived3 = 1009
        case smsReceived4 = 1010
        case smsReceived7 = 1012
        case smsReceived5 = 1013
        case smsReceived6 = 1014
        case tweetSent = 1016
        case anticipate = 1020
        case bloom = 1021
        case calypso = 1022
        case chooChoo = 1023
        case descent = 1024
        case fanfare = 1025
        case ladder = 1026
        case minuet = 1027
        case newsFlash = 1028
        case noir = 1029
        case sherwhoodForest = 1030
        case spell = 1031
        case suspense = 1032
        case telegraph = 1033
        case tiptoes = 1034
        case typewriters = 1035
        case update = 1036
        case ussd = 1050
        case simToolkitCallDropped = 1051
        case simToolkitGeneralBeep = 1052
        case simToolkitNegativeAck = 1053
        case simToolkitPositiveAck = 1054
        case simToolkitSms = 1055
        case tinkQuiet = 1057
        case ctBusy = 1070
        case ctCongestion = 1071
        case ctPathAck = 1072
        case ctError = 1073
        case ctCallWaiting = 1074
        case ctKeyTone2 = 1075
        case lock = 1100
        case unlockFailed = 1102
        case tink = 1103
        case tock = 1104
        case beepBeep = 1106
        case ringerChanged = 1107
        case photoShutter = 1108
        case shake = 1109
        case jblBegin = 1110
        case jblConfirm = 1111
        case jblCancel = 1112
        case beginRecord = 1113
        case endRecord = 1114
        case jblAmbiguous = 1115
        case jblNoMatch = 1116
        case beginVideoRecord = 1117
        case endVideoRecord = 1118
        case vcInvitationAccepted = 1150
        case vcRinging = 1151
        case vcEnded = 1152
        case ctCallWaiting2 = 1153
        case vcRingingQuiet = 1154
        case touchTone0 = 1200
        case touchTone1 = 1201
        case touchTone2 = 1202
        case touchTone3 = 1203
        case touchTone4 = 1204
        case touchTone5 = 1205
        case touchTone6 = 1206
        case touchTone7 = 1207
        case touchTone8 = 1208
        case touchTone9 = 1209
        case touchToneStar = 1210
        case touchTonePound = 1211
        case headsetStartCall = 1254
        case headsetRedial = 1255
        case headsetAnswerCall = 1256
        case headsetEndCall = 1257
        case headsetWait = 1258
        case headsetTransitionEnd = 1259
        case tockQuiet = 1306
    }
}


================================================
FILE: Sources/TapticEngine.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

@available(iOS 10.0, *)
extension Piano {
    /// First generation Taptic Engine vibrations
    public enum TapticEngine: Int {
        /// Weak boom
        case peek = 1519
        
        /// Strong boom
        case pop = 1520
        
        /// Three sequential weak booms
        case cancelled = 1521
        
        /// Weak boom then strong boom
        case tryAgain = 1102
        
        /// Three sequential strong booms
        case failed = 1107
    }
}


================================================
FILE: Sources/UIDevice+Extension.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

/// Device extension to check whether user's device supports Taptic Engine and/or Haptic Feedback
/// Be sure to use with UIDevice.current
public extension UIDevice {
    /// In order to check if the iPhone has Taptic Engine and/or Haptic Feedback support, we need to check the device's model version. This function returns the generation and version of the current device.
    /// Note: Simulators will return a result of (0, 0), resulting in the hasTapticEngine and hasHapticFeedback BOOLs returning false
    /* Example:
     "iPhone7,1" on iPhone 6 Plus -> (7, 1)
     "iPhone7,2" on iPhone 6 -> (7, 2)
     "iPhone8,1" on iPhone 6S -> (8, 1)
     "iPhone8,2" on iPhone 6S Plus -> (8, 2)
     "iPhone8,4" on iPhone SE -> (8, 4)
     "iPhone9,1" on iPhone 7 (CDMA) -> (9, 1)
     "iPhone9,3" on iPhone 7 (GSM) -> (9, 3)
     "iPhone9,2" on iPhone 7 Plus (CDMA) -> (9, 2)
     "iPhone9,4" on iPhone 7 Plus (GSM) -> (9, 4)
     iPhone 8, 8S, and X will likely use a generation of 10 or greater, and will support Haptic Feedback, so this extension will work for those devices as well.
     iPhone X -> iPhone10,6
     */
    private func getDeviceGenerationVersion() -> (generation: Int, version: Int) {
        var sysinfo = utsname()
        uname(&sysinfo)
        let platform = String(bytes: Data(bytes: &sysinfo.machine, count: Int(_SYS_NAMELEN)), encoding: .ascii)!.trimmingCharacters(in: .controlCharacters)
        if platform.lowercased().prefix("iPhone".count) != "iPhone".lowercased() { // Not an iPhone (probably simulator)
            return (0, 0)
        }
        let numbers = platform.filter { "0123456789,".contains($0) }
        if let commaIndex = numbers.index(of: ",") {
            let firstNumber = numbers[numbers.startIndex..<commaIndex]
            let afterCommaIndex = numbers.index(after: commaIndex)
            let secondNumber = numbers[afterCommaIndex..<numbers.endIndex] // endIndex is an index after the last index
            let generation = Int(firstNumber) ?? 0
            let version = Int(secondNumber) ?? 0
            return (generation, version)
        } else {
            return (0, 0)
        }
    }
    
    // Returns a BOOL value representing whether the current device has a Taptic Engine or not
    public var hasTapticEngine: Bool {
        get {
            let device = getDeviceGenerationVersion()
            if device.generation == 8 {
                if device.version == 4 { // SE
                    return false
                } else {
                    return true
                }
            } else if device.generation > 8 {
                return true
            } else {
                return false
            }
        }
    }
    
    // Returns a BOOL value representing whether the current device has a Taptic Engine with Haptic Feedback support
    public var hasHapticFeedback: Bool {
        get {
            let device = getDeviceGenerationVersion()
            if device.generation >= 9 {
                return true
            } else {
                return false
            }
        }
    }
}


================================================
FILE: Sources/Vibration.swift
================================================
// The MIT License (MIT)
//
// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import Foundation

@available(iOS 10.0, *)
extension Piano {
    /// Standard vibrations available on all models of the iPhone
    public enum Vibration: Int {
        /// Basic 1-second vibration
        case `default` = 4095
        /// Two short consecutive vibrations
        case alert = 1011
    }
}


================================================
FILE: Tests/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>BNDL</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0</string>
	<key>CFBundleVersion</key>
	<string>1</string>
</dict>
</plist>


================================================
FILE: Tests/PianoTests.swift
================================================
//
//  PianoTests.swift
//  PianoTests
//
//  Created by Saoud Rizwan on 9/11/17.
//  Copyright © 2017 Saoud Rizwan. All rights reserved.
//

import XCTest
@testable import Piano

class PianoTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
    }
    
    override func tearDown() {
        super.tearDown()
    }
    
}
Download .txt
gitextract_7isrf40a/

├── .gitignore
├── .swift-version
├── Example/
│   ├── PianoExample/
│   │   ├── AppDelegate.swift
│   │   ├── Assets.xcassets/
│   │   │   ├── AppIcon.appiconset/
│   │   │   │   └── Contents.json
│   │   │   └── Contents.json
│   │   ├── Base.lproj/
│   │   │   ├── LaunchScreen.storyboard
│   │   │   └── Main.storyboard
│   │   ├── Info.plist
│   │   ├── ResponsiveLabel.swift
│   │   ├── Sounds.xcassets/
│   │   │   ├── Contents.json
│   │   │   ├── heart.dataset/
│   │   │   │   └── Contents.json
│   │   │   ├── kiss.dataset/
│   │   │   │   └── Contents.json
│   │   │   └── wink.dataset/
│   │   │       └── Contents.json
│   │   └── ViewController.swift
│   └── PianoExample.xcodeproj/
│       ├── project.pbxproj
│       └── project.xcworkspace/
│           └── contents.xcworkspacedata
├── LICENSE
├── Piano.podspec
├── Piano.xcodeproj/
│   ├── project.pbxproj
│   ├── project.xcworkspace/
│   │   └── contents.xcworkspacedata
│   └── xcshareddata/
│       └── xcschemes/
│           └── Piano.xcscheme
├── Piano.xcworkspace/
│   └── contents.xcworkspacedata
├── README.md
├── Sources/
│   ├── Audio.swift
│   ├── HapticFeedback.swift
│   ├── Info.plist
│   ├── Note.swift
│   ├── Piano+Error.swift
│   ├── Piano.h
│   ├── Piano.swift
│   ├── SystemSound.swift
│   ├── TapticEngine.swift
│   ├── UIDevice+Extension.swift
│   └── Vibration.swift
└── Tests/
    ├── Info.plist
    └── PianoTests.swift
Condensed preview — 36 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (141K chars).
[
  {
    "path": ".gitignore",
    "chars": 361,
    "preview": "## OS X Finder\n.DS_Store\n\n## Build generated\nbuild/\nDerivedData\n\n## Various settings\n*.pbxuser\n!default.pbxuser\n*.mode1v"
  },
  {
    "path": ".swift-version",
    "chars": 4,
    "preview": "4.2\n"
  },
  {
    "path": "Example/PianoExample/AppDelegate.swift",
    "chars": 2177,
    "preview": "//\n//  AppDelegate.swift\n//  PianoExample\n//\n//  Created by Saoud Rizwan on 9/11/17.\n//  Copyright © 2017 Saoud Rizwan. "
  },
  {
    "path": "Example/PianoExample/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "chars": 3273,
    "preview": "{\n  \"images\" : [\n    {\n      \"size\" : \"20x20\",\n      \"idiom\" : \"iphone\",\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n   "
  },
  {
    "path": "Example/PianoExample/Assets.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "Example/PianoExample/Base.lproj/LaunchScreen.storyboard",
    "chars": 1719,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard"
  },
  {
    "path": "Example/PianoExample/Base.lproj/Main.storyboard",
    "chars": 7467,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<document type=\"com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB\" version=\"3"
  },
  {
    "path": "Example/PianoExample/Info.plist",
    "chars": 1408,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Example/PianoExample/ResponsiveLabel.swift",
    "chars": 273,
    "preview": "//\n//  ResponsiveLabel.swift\n//  PianoExample\n//\n//  Created by Saoud Rizwan on 10/1/18.\n//  Copyright © 2018 Saoud Rizw"
  },
  {
    "path": "Example/PianoExample/Sounds.xcassets/Contents.json",
    "chars": 62,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "Example/PianoExample/Sounds.xcassets/heart.dataset/Contents.json",
    "chars": 164,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  },\n  \"data\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"fil"
  },
  {
    "path": "Example/PianoExample/Sounds.xcassets/kiss.dataset/Contents.json",
    "chars": 156,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  },\n  \"data\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"fil"
  },
  {
    "path": "Example/PianoExample/Sounds.xcassets/wink.dataset/Contents.json",
    "chars": 159,
    "preview": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  },\n  \"data\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"fil"
  },
  {
    "path": "Example/PianoExample/ViewController.swift",
    "chars": 19996,
    "preview": "//\n//  ViewController.swift\n//  PianoExample\n//\n//  Created by Saoud Rizwan on 9/11/17.\n//  Copyright © 2017 Saoud Rizwa"
  },
  {
    "path": "Example/PianoExample.xcodeproj/project.pbxproj",
    "chars": 15443,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Example/PianoExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 157,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:PianoExample.xc"
  },
  {
    "path": "LICENSE",
    "chars": 1099,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Saoud Rizwan <hello@saoudmr.com>\n\nPermission is hereby granted, free of charge"
  },
  {
    "path": "Piano.podspec",
    "chars": 1023,
    "preview": "Pod::Spec.new do |s|\n  s.name         = \"Piano\"\n  s.version      = \"1.8\"\n  s.summary      = \"Compose a symphony of sound"
  },
  {
    "path": "Piano.xcodeproj/project.pbxproj",
    "chars": 19394,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 48;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "Piano.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 150,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:Piano.xcodeproj"
  },
  {
    "path": "Piano.xcodeproj/xcshareddata/xcschemes/Piano.xcscheme",
    "chars": 2872,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"0900\"\n   version = \"1.3\">\n   <BuildAction\n      "
  },
  {
    "path": "Piano.xcworkspace/contents.xcworkspacedata",
    "chars": 422,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Example/PianoE"
  },
  {
    "path": "README.md",
    "chars": 13381,
    "preview": "<p align=\"center\">\n    <img src=\"https://user-images.githubusercontent.com/7799382/30356431-dbba9920-97ed-11e7-8f2b-a5b5"
  },
  {
    "path": "Sources/Audio.swift",
    "chars": 1659,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/HapticFeedback.swift",
    "chars": 3309,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/Info.plist",
    "chars": 774,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Sources/Note.swift",
    "chars": 1955,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/Piano+Error.swift",
    "chars": 2226,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/Piano.h",
    "chars": 469,
    "preview": "//\n//  Piano.h\n//  Piano\n//\n//  Created by Saoud Rizwan on 9/11/17.\n//  Copyright © 2017 Saoud Rizwan. All rights reserv"
  },
  {
    "path": "Sources/Piano.swift",
    "chars": 16456,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/SystemSound.swift",
    "chars": 4123,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/TapticEngine.swift",
    "chars": 1652,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/UIDevice+Extension.swift",
    "chars": 4268,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Sources/Vibration.swift",
    "chars": 1465,
    "preview": "// The MIT License (MIT)\n//\n// Copyright (c) 2018 Saoud Rizwan <hello@saoudmr.com>\n//\n// Permission is hereby granted, f"
  },
  {
    "path": "Tests/Info.plist",
    "chars": 701,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Tests/PianoTests.swift",
    "chars": 346,
    "preview": "//\n//  PianoTests.swift\n//  PianoTests\n//\n//  Created by Saoud Rizwan on 9/11/17.\n//  Copyright © 2017 Saoud Rizwan. All"
  }
]

About this extraction

This page contains the full source code of the saoudrizwan/Piano GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 36 files (127.6 KB), approximately 34.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!