如何在iOS上使用CoreMIDI?

rryofs0p  于 2023-01-14  发布在  iOS
关注(0)|答案(3)|浏览(186)

我一直找不到很多关于iOS版CoreMIDI的信息。甚至可以通过向设备本身发送消息来播放MIDI声音吗?iPhone或iPad是否安装了MIDI设备,或者你必须连接一个设备才能与之接口?

gblwokeq

gblwokeq1#

这是几年太晚了,但它可能会帮助别人在那里,就像它帮助我一样。这个website是帮助我从外部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

附加注解

    • midi输入回调函数**
  • midiInputCallback是通过MIDI设备(键盘)发生MIDI事件时调用的函数
  • 注意:您可以从此处开始处理MIDI信息 *
    • handleMIDIStatus函数**
  • handleMIDIStatus获取MIDI数据包(其中包含有关播放内容的信息和MIDIController的示例
  • 注意:您需要对MIDIController的引用,以便可以填充类的属性...在我的示例中,我将所有播放的音符按MIDI编号存储在一个数组中,以供以后使用 *
  • status0x90时,这意味着音符已被触发,如果其力度为0,则视为未播放...我需要添加此if语句,因为它无法正常工作
  • 注意:我只处理key onkey off事件,因此您需要扩充switch语句以处理更多的MIDI事件 *
    • 更新按键后键盘按钮方法**
  • 这是我用来存储弹奏音符的方法,一旦释放键,我就从数组中删除音符

希望这能帮上忙。

a8jjtwal

a8jjtwal2#

你应该看看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兼容应用程序的消息。

u0sqgete

u0sqgete3#

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)")

    }

}

相关问题