14

我一直无法找到有关 iOS 版 CoreMIDI 的太多信息。甚至可以通过向设备本身发送消息来播放 MIDI 声音。iPhone 或 iPad 是否安装了 MIDI 设备,或者您是否必须连接设备才能与之交互?

4

3 回答 3

10

这已经晚了几年,但它可能会帮助其他人,就像它帮助了我一样。这个网站帮助我从外部 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 编号, 在一个数组中供以后使用

  • statusis 0x90,这意味着一个音符已被触发,如果它的速度为 0,则认为它没有播放...我需要添加这个 if 语句,因为它不能正常工作
    注意:我只处理key onkey off事件,所以你会增加 switch 语句来处理更多的 MIDI 事件

updateKeyboardButtonAfterKeyPressed 方法

  • 这是我用来存储播放的音符的一种方法,一旦释放键,我就会从这个数组中删除音符

我希望这有帮助。

于 2015-07-17T08:05:04.957 回答
9

你应该看看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 兼容应用程序发送或接收消息。

于 2013-01-01T11:57:55.710 回答
1
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)")

    }

}
于 2021-08-20T07:30:01.247 回答