CoreMIDIでMIDIデータを受信する
ほんとは送信したいんだけど、そのためには送信先を用意する必要があって、
まずは下準備ということでMIDIデータの受信から。
追記 2015/10/22
あまりにも用を足さないコードだったので、
まるっと書き直しました。
— ここまで追記 —
検索するといろいろ見つかったんだけど、
今回もこちらのサイトが詳しくて、このページを参考にさせて頂きました。
Core MIDI その1 MIDIObject
http://objective-audio.jp/2008/06/core-midi-midiobject.html
手順は、こんな感じ。
- 適当なプロジェクト(ex. MyMidiInst)を作る
- “Linked Frameworks and Libraries”にCoreMIDIを追加する
あとは、これを”ViewController.swift”にコピペする
(”Device Orientation”はPortrait
で固定してね。)
import UIKit import CoreMIDI class ViewController: UIViewController { let clientName: CFStringRef = "MyMidiInst" var clientRef: MIDIClientRef = 0 var extPointRef: MIDIEndpointRef = 0 var inPortRef: MIDIPortRef = 0 var textView: UITextView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. var textArea = self.view.frame textArea.insetInPlace( dx: 0.0, dy: 20.0 ) textArea.offsetInPlace( dx: 0.0, dy: 40.0 ) textView = UITextView( frame: textArea ) textView.scrollEnabled = true textView.font = UIFont.systemFontOfSize( 12.0 ) textView.textColor = UIColor.whiteColor() textView.backgroundColor = UIColor.blackColor() textView.editable = false textView.selectable = false let clearButton = UIButton(frame: CGRect(x: 0,y: 20,width: 120,height: 40) ) clearButton.backgroundColor = UIColor.blueColor() clearButton.setTitle( "Clear", forState: .Normal ) clearButton.addTarget( self, action: Selector("clearButtonTapped:"), forControlEvents: .TouchUpInside ) let scanButton = UIButton(frame: CGRect(x: 120,y: 20,width: 120,height: 40) ) scanButton.backgroundColor = UIColor.blueColor() scanButton.setTitle( "Scan", forState: .Normal ) scanButton.addTarget( self, action: Selector("scanButtonTapped:"), forControlEvents: .TouchUpInside ) let connectButton1 = UIButton(frame: CGRect(x: 240,y: 20,width: 120,height: 40) ) connectButton1.backgroundColor = UIColor.blueColor() connectButton1.setTitle( "Connect[0]", forState: .Normal ) connectButton1.addTarget( self, action: Selector("connectButtonTapped:"), forControlEvents: .TouchUpInside ) let connectButton2 = UIButton(frame: CGRect(x: 360,y: 20,width: 120,height: 40) ) connectButton2.backgroundColor = UIColor.blueColor() connectButton2.setTitle( "Connect[1]", forState: .Normal ) connectButton2.addTarget( self, action: Selector("connectButtonTapped:"), forControlEvents: .TouchUpInside ) let disconnectButton = UIButton(frame: CGRect(x: 480,y: 20,width: 120,height: 40) ) disconnectButton.backgroundColor = UIColor.blueColor() disconnectButton.setTitle( "Disconnect", forState: .Normal ) disconnectButton.addTarget( self, action: Selector("disconnectButtonTapped:"), forControlEvents: .TouchUpInside ) self.view.addSubview( textView ) self.view.addSubview( clearButton ) self.view.addSubview( scanButton ) self.view.addSubview( connectButton1 ) self.view.addSubview( connectButton2 ) self.view.addSubview( disconnectButton ) var status: OSStatus = noErr status = MIDIClientCreateWithBlock( "MyMidiInst", &clientRef, MyMIDINotifyBlock ) if status != noErr { textView.text.appendContentsOf( "cannot create MIDI client!" ) return } status = MIDIInputPortCreateWithBlock( clientRef, "MyMidiInst MIDI In", &inPortRef, MyMIDIReadBlock ) if status != noErr { textView.text.appendContentsOf( "cannot create MIDI In!" ) return } textView.text.appendContentsOf( "MIDI In opened!\n" ) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } deinit { if inPortRef != 0 { MIDIPortDispose( inPortRef ) inPortRef = 0 } if clientRef != 0 { MIDIClientDispose( clientRef ) clientRef = 0 } } func clearButtonTapped(sender: AnyObject?) { textView.text = "" } func scanButtonTapped(sender: AnyObject?) { let n = MIDIGetNumberOfSources() for var i=0; i<n; i++ { let endPointRef = MIDIGetSource( i ) var str: Unmanaged<CFString>? let status = MIDIObjectGetStringProperty( endPointRef, kMIDIPropertyDisplayName, &str ) if status == noErr { textView.text.appendContentsOf( "[\(i)]: " ) textView.text.appendContentsOf( str!.takeUnretainedValue() as String ) textView.text.appendContentsOf( "\n" ) str!.release() } } } func connectButtonTapped(sender: AnyObject?) { extPointRef = 0 let btn = sender as! UIButton let title = btn.currentTitle! let ch = title[title.endIndex.predecessor().predecessor()] if ch == "0" { if 1 <= MIDIGetNumberOfSources() { extPointRef = MIDIGetSource( 0 ) } else { self.textView.text.appendContentsOf( "MIDISources[0] not found!\n" ) return } } else { if 2 <= MIDIGetNumberOfSources() { extPointRef = MIDIGetSource( 1 ) } else { self.textView.text.appendContentsOf( "MIDISources[1] not found!\n" ) return } } let status = MIDIPortConnectSource( inPortRef, extPointRef, nil ) if status == noErr { textView.text.appendContentsOf( "Connect!\n" ) } else { textView.text.appendContentsOf( "Connent with MIDI source failed!\n" ) } } func disconnectButtonTapped(sender: AnyObject?) { if extPointRef != 0 { MIDIPortDisconnectSource( inPortRef, extPointRef ) extPointRef = 0 textView.text.appendContentsOf( "Disconnect!\n" ) } else { textView.text.appendContentsOf( "Already disconnented!\n" ) } } func MyMIDIReadBlock(packetList: UnsafePointer<MIDIPacketList>, srcConnRefCon: UnsafeMutablePointer<Void>) { let packets: MIDIPacketList = packetList.memory var packet: MIDIPacket = packets.packet for _ in 0 ..< packets.numPackets { if packet.data.0 == 0xF8 || packet.data.0 == 0xFE { continue } let str: String switch packet.length { case 1: str = String(format: "%02X", packet.data.0) case 2: str = String(format: "%02X %02X", packet.data.0, packet.data.1) case 3: str = String(format: "%02X %02X %02X", packet.data.0, packet.data.1, packet.data.2) default: str = "length: \(packet.length)" } dispatch_async( dispatch_get_main_queue(), { self.textView.text.appendContentsOf( str ) self.textView.text.appendContentsOf( "\n" ) } ) packet = MIDIPacketNext( &packet ).memory } } func MyMIDINotifyBlock(midiNotification: UnsafePointer<MIDINotification>) { self.textView.text.appendContentsOf( "MyMIDINotifyBlock called!\n" ) // todo: ここは何をしたらいんだろうね? } }
Clearボタンは置いといて、
Scanボタンをタッチすると接続先が表示されます。
常に1つ存在するみたいですが、良くわかりません。(*1)
他のアプリだと特に選択させたりしないので、
最後にiPadと接続したものが選択されてそう。
という訳で、
手元にあるreface CSをCamera Connection Kitで接続して、
Scanボタンをタッチすると2つ目に表示されました。
これと接続するには、Connect[1]をタッチします。
すると、大量にデータが送られてくるので、
一定間隔で送られてくる0xF8
と0xFE
は無視して、
他の受信データはヘキサで表示してみました。
表示されている文字を消す場合はClearボタンをタッチして、
接続を解除する場合はDisconnectボタンをタッチします。
MyMIDIReadBlock
の辺りが自信ないというか、
Swiftで書かれたサンプルが見つからなくて、
やっとコンパイルできたレベルなので、ちょっとアレです。
あと、受信データはMIDIPacket
で確実に区切られていれば良いのですが、
そうでなければ、この実装のままだと取りこぼしてそう・・・。
おしまい。
(*1) 自分の環境だと、「ネットワーク Session 1」が常に存在する
Leave a Comment