【发布时间】:2012-12-19 12:06:38
【问题描述】:
我一直无法找到有关 iOS 版 CoreMIDI 的更多信息。甚至可以通过向设备本身发送消息来播放 MIDI 声音。 iPhone 或 iPad 是否安装了 MIDI 设备,或者您是否必须连接设备才能与之连接?
【问题讨论】:
我一直无法找到有关 iOS 版 CoreMIDI 的更多信息。甚至可以通过向设备本身发送消息来播放 MIDI 声音。 iPhone 或 iPad 是否安装了 MIDI 设备,或者您是否必须连接设备才能与之连接?
【问题讨论】:
这已经晚了几年,但它可能会帮助其他人,就像它帮助了我一样。这个website 帮助我从外部 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 事件时调用的函数handleMIDIStatus 函数
handleMIDIStatus 获取 MIDI 数据包(其中包含有关播放内容和 MIDIController 实例的信息
注意:您需要对 MIDIController 的引用,以便您可以填充类的属性...在我的情况下,我将所有播放的音符按 MIDI 编号存储在一个数组中以供以后使用
当status为0x90时,表示一个音符已被触发,如果它的力度为0,则认为它没有播放...我需要添加这个if语句,因为它是'无法正常运行
注意:我只处理 key on 和 key off 事件,因此您可以增加 switch 语句以处理更多 MIDI 事件
updateKeyboardButtonAfterKeyPressed 方法
我希望这会有所帮助。
【讨论】:
你应该看看pete goodliffe's blog,他慷慨地提供了一个示例项目。 它对我开始编写 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)")
}
}
【讨论】: