iPod20台を通信/制御/連携してみた【Max × Swift × OSC】

##はじめに

この記事は、[Life is Tech ! アドベントカレンダー2016](http://qiita.com/advent-calendar/2016/life-is-tech) 23日目の記事です.

はじめまして岐阜のiOSメンター __[watakemi](https://twitter.com/watakemi725)__です.
普段はIAMASという学校でひっそりと息をしています.
そんな私が最近取り組んでいるのは大量のiPodを制御することです.大量にピカらせてます.

![ipodjockeyPhotoISCA2.png](https://qiita-image-store.s3.amazonaws.com/0/59590/a3692490-10b8-fc1d-18ef-1803cfc59cfa.png "ipodjockeyPhotoISCA2.png")
![スクリーンショット 2016-12-23 14.37.14.png](https://qiita-image-store.s3.amazonaws.com/0/59590/800d0a60-19b3-c39c-fef4-7912becc99d0.png "スクリーンショット 2016-12-23 14.37.14.png")
VJやデコみたいに光らすことも
![](http://i.giphy.com/3oz8xJ9HBIGV2MJkxq.gif "")


今回はMaxやswiftにおけるOSCの送受信をベースに大量の端末を制御するときの知見を共有できたらと思います.



##本記事について
OSCまわりで横断的にまとめられている記事が少なかったのでこの際まとめてみました.
Maxの基本操作やSwiftの基礎についてはあまり触れません、ごめんなさい.

* OSC通信
* Max,swiftでの実装
* 通信まわりとブロードキャスト
* 実践例

##OSC通信って
OSC(Open Sound Control)とは本来電子楽器同士を連携させるために開発された通信プロトコル.
__複数端末間で信号が送受信可能__、かつ超便利と簡単シンプルということから、
今では様々なソフトを架け橋するために利用されるようになりました.
詳しくは[田所先生の記事](http://yoppa.org/ma2_10/2279.html)がとても詳しく解説されています.




##【基礎】MaxにおけるOSC送受信
[Max](https://cycling74.com/products/max/#.WFzEd7aLSHo)では一般的には以下のようなパッチを組むと送受信することがいとも簡単にできてします.
OSC通信でデバッグや簡単な通信を試してみたいときは簡単に試すことができるのでオススメです.
買わずとも[体験版](https://cycling74.com/downloads/#.WFzEVraLSHo)では"保存する"以外使えるので触ったことない方は是非試してみてください.
###実装
####１つの値を送る場合
__"udpsend"__によりOSCメッセージを送ることが可能
IPアドレス : 192.168.1.255
port : 3333

![スクリーンショット 2016-12-23 12.36.38.png](https://qiita-image-store.s3.amazonaws.com/0/59590/7ebcf3ea-5962-e7d0-d3a3-cefe6062feed.png "スクリーンショット 2016-12-23 12.36.38.png")

####複数の値を送る場合
__"sprintf"__により値をまとめる
__"%s %f"__はそれぞれ"整数 小数"を意味する
![スクリーンショット 2016-12-23 12.36.45.png](https://qiita-image-store.s3.amazonaws.com/0/59590/f1b9c9c2-66cf-a079-191b-fd0938a6c741.png "スクリーンショット 2016-12-23 12.36.45.png")


####受信するやで
__"udpreceive"__によりOSCメッセージを受信することが可能(portを指定)
__"route /OSCメッセージ"__によりOSCメッセージの場合分け可能
__"uppack"__により複数の値を切り分けが可能
__"0 0."__はそれぞれ"整数 小数"の型を指定	
![スクリーンショット 2016-12-23 12.38.58.png](https://qiita-image-store.s3.amazonaws.com/0/59590/fb3f1a47-6c59-f3f7-4111-45f65aeb5363.png "スクリーンショット 2016-12-23 12.38.58.png")


###余談
外部ライブラリ"[OSC-route](http://cnmat.berkeley.edu/patch/4029)"の使用で楽ちんだったりもしますよう.

![スクリーンショット 2016-12-23 12.16.03.png](https://qiita-image-store.s3.amazonaws.com/0/59590/cc7de2e0-f392-e7df-7cba-34e4c8726dbe.png "スクリーンショット 2016-12-23 12.16.03.png")


##【基礎】swiftにおけるOSC送受信
iOSアプリでのOSCのやりとりは"[buzzOSC](https://github.com/marsman12019/buzzOSC)"というライブラリを使用することで簡単に実装します
###導入手順
1.buzzOSCファイル内の"F53OSC"を丸ごと自分のXcodeプロジェクトファイルの中にぶっ込みます
![スクリーンショット 2016-04-22 23.30.42.png](https://qiita-image-store.s3.amazonaws.com/0/59590/5842d076-e223-4cb2-55b8-fd4d03d92bcd.png "スクリーンショット 2016-04-22 23.30.42.png")
2.ブリッジングヘーダーを作成して以下のように入力しましょう.
(詳しいbridgingheader導入の仕方は[こちら](http://tech.admax.ninja/2014/09/26/about-bridging-header/))

```(プロジェクト名)_Bridging_Header.h
#import "F53OSC.h"
```
###実装
####OSC送信すんで
```OscSendViewConroller.swift
import UIKit

class OscSendViewController: UIViewController {

    //osc送信のためのoscClientの初期化
    let oscClient = F53OSCClient.init()
   
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //OSCを送りたい先のIPアドレスを指定
        oscClient.host = "192.000.0.000"
        //贈りたい先のport番号を指定
        oscClient.port = 3333
        
    }
    
    @IBAction func sendOSC(){
        //123というメッセージをOSCで送信
        self.sendMessage(oscClient, addressPattern: "/value", arguments: [123])
        //複数の値を送る場合はarguments:[123,231,312]
    }

    //osc送信のためのメソッド
    func sendMessage(client: F53OSCClient, addressPattern: String, arguments: [AnyObject]) {
        let message = F53OSCMessage(addressPattern: addressPattern, arguments: arguments)
        client.sendPacket(message)
        print("Sent '\(message)' to \(client.host):\(client.port)")
    }
}

```

####OSC受信するわ
```OscRecieveViewController.swift
import UIKit

//デリゲートまわりでF53OSCPacketDestinationの追加
class OscRecieveViewController: UIViewController,F53OSCPacketDestination {
    
    //osc受信のためのoscServerの初期化
    let oscServer = F53OSCServer.init()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //portの指定など
        oscServer.port = 3333
        oscServer.delegate = self
        if oscServer.startListening() {
            print("Listening for messages on port \(oscServer.port)")
        } else {
            print("Error: Server was unable to start listening on port \(oscServer.port)")
        }
    }
    
    //OSC受信するためのメソッド
    func takeMessage(message: F53OSCMessage!) {
        
        print(message)
        
        //OSCmessageによる比較
        if message.addressPattern == "/value" {
            
            //OSCargumentsによる比較
            if message.arguments[0] as! Int == 123{
                print("hello 123")
            }else if message.arguments[0] as! Int == 321{
                print("hello 321")
            }
        }
    }

}

```
以上がswiftによるOSC送受信の基礎的な解説でした.

##通信方法とブロードキャスト
OSC通信ではメッセージを贈りたい相手のIPアドレスを指定してメッセージを送っていましたが30台のiPodのIPアドレスなんて…となりますよね　

###１.各々のIPアドレスを吐き出してもらう
iOSアプリ側でアプリ起動したときに自分のIPアドレスをMacPCのIPドレス宛に送ってもらう案
####ブリッジングヘーダー(.h)内に追加するコード
```(プロジェクト名)_Bridging_Header.h
#include <ifaddrs.h> 
```
####自身のIPアドレス取得メソッド
```ViewController.swift
 func getIFAddresses() -> [String] {
        var addresses = [String]()
        var ifaddr : UnsafeMutablePointer<ifaddrs> = nil
        
        if getifaddrs(&ifaddr) == 0 {
            for (var ptr = ifaddr; ptr != nil; ptr = ptr.memory.ifa_next) {
                let flags = Int32(ptr.memory.ifa_flags)
                var addr = ptr.memory.ifa_addr.memory
                
                if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) {
                    if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) {
                        var hostname = [CChar](count: Int(NI_MAXHOST), repeatedValue: 0)
                        
                        if (getnameinfo(&addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),
                            nil, socklen_t(0), NI_NUMERICHOST) == 0) {
                            if let address = String.fromCString(hostname) {
                                addresses.append(address)
                            }
                        }
                    }
                }
            }
            freeifaddrs(ifaddr)
        }
        print(addresses)
        return addresses
        
    }
```
####メソッドの使い方
```ViewController.swift
let  myIPaddress = getIFAddresses()[0]
```
あとは制御しているMacPC宛てに端末IPアドレスを送りつけて、oFアプリであれば受信したIPアドレスを配列にまとめて、一括管理可能なんてことが可能ですね.
でもめんどうという方は次のやり方！！

###２.ブロードキャストアドレス
ブロードキャストアドレスとは
__同じネットワークに繋がっている全ての端末にメッセージを送りたいときに使う特殊なアドレス__

宛先IPアドレスを __"XXX.XXX.1.255"__ にすることでブロードキャストが実現可能です.(他にもやり方はあったり…)
(XXX.XXXは端末のIPアドレス上2つ数字を入力しください.例としは"192.168.1.255","10.10.1.255" となるはずです)

※ただし注意することとしては大学のLANとかでブロードキャストアドレスにメッセージを送るのはやめましょう.
他人がキャッチしてしまったり迷惑極まりないので気を付けましょう(めっちゃ飛ばして怒られました)

##20台繋げるときに気をつけること

###ad-hoc接続では限界がある
OSC通信は通常同じネットワークに繋がっていないと通信がうまくいきません.
そのためさまざまな現場に行ったときは自分のPCをルーター代わりにad-hoc通信をすることがよくあります.
いわゆる[モバイルアドホックネットワーク](https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB%E3%82%A2%E3%83%89%E3%83%9B%E3%83%83%E3%82%AF%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF)なこと.

少し詳しく言うと以下のようにネットワークをMacで作成することですね.
![スクリーンショット 2016-12-23 14.00.29.png](https://qiita-image-store.s3.amazonaws.com/0/59590/5ce4f17c-8ea6-a429-0cf0-9071b58b4b83.png "スクリーンショット 2016-12-23 14.00.29.png")
![スクリーンショット 2016-12-23 18.10.39.png](https://qiita-image-store.s3.amazonaws.com/0/59590/ad05c0d3-4a80-b260-61bb-ddce028ee87e.png "スクリーンショット 2016-12-23 18.10.39.png")



ですがad-hoc接続では通信量に限界があり、多くて13〜15台の接続で __遅延や
メッセージが届かないアクシデント([パケットロス](https://www.nhk.or.jp/strl/publica/giken_dayori/jp2/qa-0604.html))__が多発してしまいます.
###AirMacExtremeとかを使うと余裕マン

それを解決するために使用したのが通信容量の大きいApple純正無線LANルーター __"Air Mac Extreme"__

![AirMacExtreme_image-1.jpg](https://qiita-image-store.s3.amazonaws.com/0/59590/c15e64f5-909b-cf1e-4393-311d097f497e.jpeg "AirMacExtreme_image-1.jpg")


こちらに全てのiPodと制御用PCを繋げることで全台目立つ遅延なく通信ができました.

Appleさん曰くは __50台__まで接続が可能ということで、20台以上でもきっと __ヌルサク__に通信をすることが可能でしょう.
またad-hoc接続しているとネットワークを普通に繋げられないので毎回wifiを切り替えてはセットアップが必要という手間が省けるので素敵です.

##実践例
できることは未知数っていうのが正直な所

![](http://i.giphy.com/3o6Ztrl98Y4X71YtHi.gif "")


また個別のデバイスごとに __個別ID__をもたせると集合体のような動きが実現可能(制作途中の汚い写真でごめんなさい)
![ipjID.png](https://qiita-image-store.s3.amazonaws.com/0/59590/593736ba-be62-6f46-7858-6a01cf2cbdc6.png "ipjID.png")

下のように徐々に信号が伝わっていく光らせ方が可能となります
![](http://i.giphy.com/l4JyZy5gHwkAPXnQQ.gif)


##最後に
さいごまで読んでいただきありがとうございます、
20台もiPodなんてねーよって方がたくさんだと思いますが、友達同士でMacに同じプロジェクトを配って
同じwifiに繋げて同じアプリ立ち上げれば似たようなことができます.忘年会とかでも使えるかもですね.
この記事が少しでも誰かの手助けorインスピレーションにつながれば幸いです.
ありがとうございました！コメントいいねストックお待ちしていおります！

さて明日は"東工大のハッカソン荒らし"またの名を"oF王子"こと"ぬま"です、お楽しみにー！