CoreMIDI for iOS に関する情報はあまり見つかりませんでした。デバイス自体にメッセージを送信して MIDI サウンドを再生することさえ可能ですか? iPhone または iPad には MIDI デバイスがインストールされていますか、それともインターフェイスするためにデバイスを接続する必要がありますか?
3 に答える
これは数年遅すぎますが、私を助けてくれたように、他の誰かを助けるかもしれません. このWeb サイトは、外部 MIDI キーボードから MIDI データを読み取るのに役立ちました。接続は最も難しい部分ですが、このチュートリアルでは順を追って説明します。
これが私が作成したクラスです。
MIDIController.h
#import <Foundation/Foundation.h>
@interface MIDIController : NSObject
@property NSMutableArray *notes;
@end
MIDIController.m
#import "MIDIController.h"
#include <CoreFoundation/CoreFoundation.h>
#import <CoreMIDI/CoreMIDI.h>
#define SYSEX_LENGTH 1024
#define KEY_ON 1
#define KEY_OFF 0
@implementation MIDIController
- (id)init {
if (self = [super init]) {
_notes = [[NSMutableArray alloc] init];
[self setupMidi];
}
return self;
}
- (void) setupMidi {
MIDIClientRef midiClient;
checkError(MIDIClientCreate(CFSTR("MIDI client"), NULL, NULL, &midiClient), "MIDI client creation error");
MIDIPortRef inputPort;
checkError(MIDIInputPortCreate(midiClient, CFSTR("Input"), midiInputCallback, (__bridge_retained void *)self, &inputPort), "MIDI input port error");
checkError(connectMIDIInputSource(inputPort), "connect MIDI Input Source error");
}
OSStatus connectMIDIInputSource(MIDIPortRef inputPort) {
unsigned long sourceCount = MIDIGetNumberOfSources();
for (int i = 0; i < sourceCount; ++i) {
MIDIEndpointRef endPoint = MIDIGetSource(i);
CFStringRef endpointName = NULL;
checkError(MIDIObjectGetStringProperty(endPoint, kMIDIPropertyName, &endpointName), "String property not found");
checkError(MIDIPortConnectSource(inputPort, endPoint, NULL), "MIDI not connected");
}
return noErr;
}
void midiInputCallback(const MIDIPacketList *list, void *procRef, void *srcRef) {
MIDIController *midiController = (__bridge MIDIController*)procRef;
UInt16 nBytes;
const MIDIPacket *packet = &list->packet[0]; //gets first packet in list
for(unsigned int i = 0; i < list->numPackets; i++) {
nBytes = packet->length; //number of bytes in a packet
handleMIDIStatus(packet, midiController);
packet = MIDIPacketNext(packet);
}
}
void handleMIDIStatus(const MIDIPacket *packet, MIDIController *midiController) {
int status = packet->data[0];
//unsigned char messageChannel = status & 0xF; //16 possible MIDI channels
switch (status & 0xF0) {
case 0x80:
updateKeyboardButtonAfterKeyPressed(midiController, packet->data[1], KEY_OFF);
break;
case 0x90:
//data[2] represents the velocity of a note
if (packet->data[2] != 0) {
updateKeyboardButtonAfterKeyPressed(midiController, packet->data[1], KEY_ON);
}//note off also occurs if velocity is 0
else {
updateKeyboardButtonAfterKeyPressed(midiController, packet->data[1], KEY_OFF);
}
break;
default:
//NSLog(@"Some other message");
break;
}
}
void updateKeyboardButtonAfterKeyPressed(MIDIController *midiController, int key, bool keyStatus) {
NSMutableArray *notes = [midiController notes];
//key is being pressed
if(keyStatus) {
[notes addObject:[NSNumber numberWithInt:key]];
}
else {//key has been released
for (int i = 0; i < [notes count]; i++) {
if ([[notes objectAtIndex:i] integerValue] == key) {
[notes removeObjectAtIndex:i];
}
}
}
}
void checkError(OSStatus error, const char* task) {
if(error == noErr) return;
char errorString[20];
*(UInt32 *)(errorString + 1) = CFSwapInt32BigToHost(error);
if(isprint(errorString[1]) && isprint(errorString[2]) && isprint(errorString[3]) && isprint(errorString[4])) {
errorString[0] = errorString[5] = '\'';
errorString[6] = '\0';
}
else
sprintf(errorString, "%d", (int)error);
fprintf(stderr, "Error: %s (%s)\n", task, errorString);
exit(1);
}
@end
その他の注意事項
midiInputCallback 関数
midiInputCallback
MIDI デバイス (キーボード) を介して MIDI イベントが発生したときに呼び出される関数です
。 注: ここから MIDI 情報の処理を開始できます。
handleMIDIStatus 関数
handleMIDIStatus
MIDIパケットを取得します(これには、再生されたものに関する情報とMIDIControllerのインスタンスが含まれます)
注:クラスのプロパティを設定できるように、MIDIControllerへの参照が必要です...私の場合、再生されたすべてのノートをMIDI番号で保存します、後で使用するための配列内が の場合、これ
status
は0x90
ノートがトリガーされたことを意味し、ベロシティが 0 の場合は再生されていないと見なされます...適切に機能していなかったので、この if ステートメントを追加する必要がありました
注:key on
とkey off
イベントのみを処理します。したがって、より多くの MIDI イベントを処理するために switch ステートメントを拡張します。
updateKeyboardButtonAfterKeyPressed メソッド
- これは、演奏されたノートを保存するために使用した方法であり、キーが離されると、この配列からノートを削除します
これが役立つことを願っています。
pete goodliffe のブログを参照してください。彼はサンプル プロジェクトを惜しみなく提供しています。CoreMIDI のプログラミングを始めるのに大いに役立ちました。
さて、ご質問ですが、iOSでは主にCoreMIDIネットワークセッションを利用しています。同じ「ネットワーク セッション」の参加者は、互いにメッセージを送信します。
たとえば、Mac でネットワーク セッションを構成し (Audio MIDI 設定ツールを使用)、iOS デバイスをそれに接続できます。このようにして、iOS から OSX ホストに、またはその逆にメッセージを送信できます。
CoreMIDI ネットワーク セッションは、RTP プロトコルを使用して MIDI メッセージを転送し、Bonjour を使用してホストを検出します。
その次に、CoreMIDI はシステムに接続された MIDI インターフェイスも処理できますが、iOS デバイスにはデフォルトで物理的な MIDI インターフェイスがありません。iPhone をシンセサイザーに直接接続する場合は、外部ハードウェアを購入する必要があります。ただし、iPad は、カメラ キットを介して USB クラス準拠の Midi インターフェイスに接続できます。
別のこととして、スタンドアロンの iOS デバイスでは、ローカルの CoreMIDI セッションを使用して、別の CoreMIDI 互換アプリケーションとの間でメッセージを送受信できます。
import UIKit
import CoreMIDI
class ViewController : UIViewController {
// MARK: - Properties -
var inputPort : MIDIPortRef = 0
var source : MIDIDeviceRef = 0
var client = MIDIClientRef()
var connRefCon : UnsafeMutableRawPointer?
var endpoint : MIDIEndpointRef?
// MARK: - Lifecycle -
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("viewDidLoad")
// endpoint
self.endpoint = MIDIGetSource(MIDIGetNumberOfSources()-1)
// USB Device References
let sources = getUSBDeviceReferences()
if sources.count > 0 {
self.source = sources.first!
}
print("source: \(source)")
// create client
DispatchQueue.global().async {
self.createClient()
}
}
// MARK: - USB Device References -
/// Filters all `MIDIDeviceRef`'s for USB-Devices
private func getUSBDeviceReferences() -> [MIDIDeviceRef] {
var devices = [MIDIDeviceRef]()
for index in 0 ..< MIDIGetNumberOfDevices() {
print("index: \(index)")
let device = MIDIGetDevice(index)
var list : Unmanaged<CFPropertyList>?
MIDIObjectGetProperties(device, &list, true)
if let list = list {
let dict = list.takeRetainedValue() as! NSDictionary
print("dict: \(dict)")
if dict["USBLocationID"] != nil {
print("USB MIDI DEVICE")
devices.append(device)
}
}
}
return devices
}
// MARK: - Client -
func createClient() {
print("createClient")
let clientName = "Client" as CFString
let err = MIDIClientCreateWithBlock(clientName, &client) { (notificationPtr: UnsafePointer<MIDINotification>) in
let notification = notificationPtr.pointee
print("notification.messageID: \(notification.messageID)")
switch notification.messageID {
case .msgSetupChanged: // Can ignore, really
break
case .msgObjectAdded:
let rawPtr = UnsafeRawPointer(notificationPtr)
let message = rawPtr.assumingMemoryBound(to: MIDIObjectAddRemoveNotification.self).pointee
print("MIDI \(message.childType) added: \(message.child)")
case .msgObjectRemoved:
let rawPtr = UnsafeRawPointer(notificationPtr)
let message = rawPtr.assumingMemoryBound(to: MIDIObjectAddRemoveNotification.self).pointee
print("MIDI \(message.childType) removed: \(message.child)")
case .msgPropertyChanged:
let rawPtr = UnsafeRawPointer(notificationPtr)
let message = rawPtr.assumingMemoryBound(to: MIDIObjectPropertyChangeNotification.self).pointee
print("MIDI \(message.object) property \(message.propertyName.takeUnretainedValue()) changed.")
case .msgThruConnectionsChanged:
fallthrough
case .msgSerialPortOwnerChanged:
print("MIDI Thru connection was created or destroyed")
case .msgIOError:
let rawPtr = UnsafeRawPointer(notificationPtr)
let message = rawPtr.assumingMemoryBound(to: MIDIIOErrorNotification.self).pointee
print("MIDI I/O error \(message.errorCode) occurred")
default:
break
}
}
// createInputPort from client
self.createInputPort(midiClient: self.client)
if err != noErr {
print("Error creating MIDI client: \(err)")
}
// run on background for connect / disconnect
let rl = RunLoop.current
while true {
rl.run(mode: .default, before: .distantFuture)
}
}
// MARK: - Input Port -
func createInputPort(midiClient: MIDIClientRef) {
print("createInputPort: midiClient: \(midiClient)")
MIDIInputPortCreateWithProtocol(
midiClient,
"Input Port" as CFString,
MIDIProtocolID._1_0,
&self.inputPort) { [weak self] eventList, srcConnRefCon in
//
let midiEventList: MIDIEventList = eventList.pointee
//print("srcConnRefCon: \(srcConnRefCon)")
//print("midiEventList.protocol: \(midiEventList.protocol)")
var packet = midiEventList.packet
//print("packet: \(packet)")
(0 ..< midiEventList.numPackets).forEach { _ in
//print("\(packet)")
let words = Mirror(reflecting: packet.words).children
words.forEach { word in
let uint32 = word.value as! UInt32
guard uint32 > 0 else { return }
let midiPacket = MidiPacket(
command: UInt8((uint32 & 0xFF000000) >> 24),
channel: UInt8((uint32 & 0x00FF0000) >> 16),
note: UInt8((uint32 & 0x0000FF00) >> 8),
velocity: UInt8(uint32 & 0x000000FF))
print("----------")
print("MIDIPACKET")
print("----------")
midiPacket.printValues()
}
}
}
MIDIPortConnectSource(self.inputPort, self.endpoint ?? MIDIGetSource(MIDIGetNumberOfSources()-1), &self.connRefCon)
}
}
class MidiPacket : NSObject {
var command : UInt8 = 0
var channel : UInt8 = 0
var note : UInt8 = 0
var velocity : UInt8 = 0
init(command: UInt8, channel: UInt8, note: UInt8, velocity: UInt8) {
super.init()
self.command = command
self.channel = channel
self.note = note
self.velocity = velocity
}
func printValues(){
print("command: \(self.command)")
print("channel: \(self.channel)")
print("note: \(self.note)")
print("velocity: \(self.velocity)")
}
}