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