Swiftでシステムサウンドを再生する


ここでは、AudioServicesPlaySystemSoundを利用してシステムサウンド（効果音）を再生しています。

### 事前確認


AudioServicesPlaySystemSound関数を使用するには以下のような制限があるので事前に確認しておきます。


> You can use System Sound Services to play short (30 seconds or shorter) sounds. The interface does not provide level, positioning, looping, or timing control, and does not support simultaneous playback: You can play only one sound at a time. You can use System Sound Services to provide audible alerts. On some iOS devices, alerts can include vibration. 

> 出典：[System Sound Services Reference](https://developer.apple.com/library/mac/documentation/AudioToolbox/Reference/SystemSoundServicesReference/index.html#//apple_ref/doc/uid/TP40007916)

・長さが30秒以下
・リニアPCMまたはIMA4（IMA/ADPCM）フォーマット
・.caf、.aif、.wavファイルのパッケージ
・音声は現在のシステムオーディオボリュームで再生され、プログラムでのボリューム制御はできません
・音声はすぐに再生されます
・ループやステレオポジショニングは利用できません
・同時再生は利用できず、一度に一つの音声しか再生できません

出典：[System Sound Services](http://secondflush2.blog.fc2.com/blog-entry-905.html)

### システムサウンドを再生する

```swift
let soundIdRing:SystemSoundID = 1000  // new-mail.caf
AudioServicesPlaySystemSound(soundIdRing)
```
SystemSoundIDを指定して、AudioServicesPlaySystemSoundを呼び出すことでシステムサウンド（バイブレーション有り）を再生すことができます。
[iOSのシステムサウンド一覧がここ](http://iphonedevwiki.net/index.php/AudioServices)に記載されています。

システムサウンドは再生せずに、バイブレーションだけを利用する場合はsoundIdRingを下記のように定義することで実現できます。

```swift
let soundIdRing:SystemSoundID = SystemSoundID(kSystemSoundID_Vibrate)
```

### バイブレーションの無いシステムサウンドを再生する

システムに含まれるサウンドファイルを直接指定することで、バイブレーションがないシステムサウンドを再生することができます。


```swift
var soundIdRing:SystemSoundID = 0
let soundUrl = NSURL(fileURLWithPath: "/System/Library/Audio/UISounds/new-mail.caf")
AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)
AudioServicesPlaySystemSound(soundIdRing)
```

ここで設定されるSystemSoundIDを確認してみると4097になっており、直接指定したSystemSoundIDとは違う値になっていることが確認できます。

### Bundleしたサウンドを再生する

システムサウンドではなく、Bundleするサウンドを再生する場合は下記のようになります

```swift
var soundIdRing:SystemSoundID = 0
let soundUrl = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("new-mail", ofType:"caf")!)
AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)
AudioServicesPlaySystemSound(soundIdRing)
//AudioServicesDisposeSystemSoundID(soundIdRing)
```

### 繰り返しサウンドを再生する

AudioServicesAddSystemSoundCompletionを利用すると、繰り返しサウンドを再生できるようなのなので試してみます。
まずは下記のコードを試してみます。

```swift
override func viewDidLoad() {
    super.viewDidLoad()

    var soundIdRing:SystemSoundID = 0
    let soundUrl = NSURL(fileURLWithPath: "/System/Library/Audio/UISounds/new-mail.caf")
    AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)

    AudioServicesAddSystemSoundCompletion(soundIdRing, nil, nil, completionCallback, nil)
}


func completionCallback(ssID:SystemSoundID,clientData:UnsafeMutablePointer<Void>) -> Void {
        
}

```

このコードは以下のようなErrorが出て実行できません。
> '(SystemSoundID, clientData: UnsafeMutablePointer<Void>) -> Void' is not convertible to 'AudioServicesSystemSoundCompletionProc'

AudioServicesAddSystemSoundCompletionのメソッドの定義を確認すると、下記のようになっています。

```swift
func AudioServicesAddSystemSoundCompletion(_ inSystemSoundID: SystemSoundID,
                                         _ inRunLoop: CFRunLoop!,
                                         _ inRunLoopMode: CFString!,
                                         _ inCompletionRoutine: AudioServicesSystemSoundCompletionProc,
                                         _ inClientData: UnsafeMutablePointer<Void>) -> OSStatus 
```
ちなみにobjcでの定義は、下記のようになっています。

```objc
 OSStatus AudioServicesAddSystemSoundCompletion ( SystemSoundID inSystemSoundID, CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void *inClientData ); 
```

AudioServicesSystemSoundCompletionProcの定義は下記のようになっています。

```swift
/*!
    @typedef        AudioServicesSystemSoundCompletionProc
    @abstract       A function to be executed when a SystemSoundID finishes playing.
    @discussion     AudioServicesSystemSoundCompletionProc may be provided by client application to be
                    called when a SystemSoundID has completed playback.
    @param          ssID
                        The SystemSoundID that completed playback
    @param          userData
                        Client application user data
*/

typealias AudioServicesSystemSoundCompletionProc = CFunctionPointer<((SystemSoundID, UnsafeMutablePointer<Void>) -> Void)>

```

このままでは実行できないので、 https://gist.github.com/jparishy/7b76edf8d0fcca1d63b0 を参考に実装してみます。
ここでは上記のgistでにある、FunctionPointer.h と FunctionPointer.m はそのまま利用します。
Bridging-Headerを追加してFunctionPointer.hはをimportする必要があります。
下記はSwift側で3回サウンドを再生する部分です。

```swift

import UIKit
import AudioToolbox

class ViewController: UIViewController {

    var functionPointer: AudioServicesCompletionFunctionPointer?
    var soundCounter = 3
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        var soundIdRing:SystemSoundID = 0
        let soundUrl = NSURL(fileURLWithPath: "/System/Library/Audio/UISounds/new-mail.caf")
        AudioServicesCreateSystemSoundID(soundUrl, &soundIdRing)
        
        
        let block = { [unowned self] (systemSoundID: SystemSoundID, userData: UnsafeMutablePointer<Void>) -> () in
            (self.soundCounter)--
            if self.soundCounter > 0 {
                NSThread.sleepForTimeInterval(0.5)
                AudioServicesPlaySystemSound(soundIdRing)
            } else {
                AudioServicesRemoveSystemSoundCompletion(soundIdRing)
            }
        }
        

        var vself = self
        let userData = withUnsafePointer(&vself, {
            (ptr: UnsafePointer<ViewController>) -> UnsafeMutablePointer<Void> in
            return unsafeBitCast(ptr, UnsafeMutablePointer<Void>.self)
        })
        
        self.functionPointer = AudioServicesCompletionFunctionPointer(systemSoundID: soundIdRing, block: block, userData: userData)
        AudioServicesAddSystemSoundCompletion(soundIdRing, CFRunLoopGetMain(), kCFRunLoopCommonModes, AudioServicesCompletionFunctionPointer.completionHandler(), userData)
        
        AudioServicesPlaySystemSound(soundIdRing)
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}
```

ここで作成したサンプルプロジェクトを[こちら](https://github.com/hideji/SystemSoundSample)に置いてあります。

### 感想

AudioServicesAddSystemSoundCompletionを利用するのが大変でした。この書き方ならObjective-Cで書いた方が簡単に書けると思います。
もう少し簡単に書ける方法が無いかは引き続き調査していきたいところです。

### 参考URL

* https://gist.github.com/jparishy/7b76edf8d0fcca1d63b0
* [[Swift]バイブレーションでぶるぶる(((( ´･ω･`)))](http://qiita.com/madobon/items/b5bc40d186ec398c7dd7)
* [【Xcode】iPhoneを連続して振動させる](http://psychlab.jugem.jp/?eid=428)
* [Programming iOS 8のサンプルコード](https://github.com/mattneub/Programming-iOS-Book-Examples/tree/master/bk2ch14p640systemSound/ch27p910systemSound)