我一直无法找到有关 iOS 版 CoreMIDI 的太多信息。甚至可以通过向设备本身发送消息来播放 MIDI 声音。iPhone 或 iPad 是否安装了 MIDI 设备,或者您是否必须连接设备才能与之交互?
3 回答
这已经晚了几年,但它可能会帮助其他人,就像它帮助了我一样。这个网站帮助我从外部 MIDI 键盘读取 MIDI 数据。连接是最棘手的部分,但本教程将引导您完成它。
这是我创建的课程。
MIDI控制器.h
#import <Foundation/Foundation.h>
@interface MIDIController : NSObject
@property NSMutableArray *notes;
@end
MIDI控制器.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 信息的地方
处理MIDI状态函数
handleMIDIStatus
获取 MIDI 数据包(其中包含有关播放内容和 MIDIController 实例的信息
注意:您需要对 MIDIController 的引用,以便您可以填充类的属性......在我的情况下,我存储所有播放的音符,按 MIDI 编号, 在一个数组中供以后使用当
status
is0x90
,这意味着一个音符已被触发,如果它的速度为 0,则认为它没有播放...我需要添加这个 if 语句,因为它不能正常工作
注意:我只处理key on
和key off
事件,所以你会增加 switch 语句来处理更多的 MIDI 事件
updateKeyboardButtonAfterKeyPressed 方法
- 这是我用来存储播放的音符的一种方法,一旦释放键,我就会从这个数组中删除音符
我希望这有帮助。
你应该看看pete goodliffe 的博客,他慷慨地提供了一个示例项目。它对我开始编写 CoreMIDI 帮助很大。
现在关于您的问题,在 iOS 上,主要使用 CoreMIDI 网络会话。同一“网络会话”的参与者相互发送消息。
例如,您在 Mac 上配置网络会话(使用音频 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)")
}
}