考虑到你自己的 MIDI 客户端创建应用程序可能会崩溃,或者主机发送 MIDI 也可能会崩溃。你可以通过检查客户端/目标是否已经存在,然后通过处理单例分配来更轻松地处理这个问题。当你的 MIDI 客户端存在但不工作时,这是因为你需要告诉 CoreMidi 你的定制客户端能够处理什么和它将具有什么延迟,特别是当主机发送客户端使用时间戳很多时(例如 Ableton 和其他软件)。
在你的 .h 文件中。
#import <CoreMIDI/CoreMIDI.h>
#import <CoreAudio/HostTime.h>
@interface YourVirtualMidiHandlerObject : NSObject
@property (assign, nonatomic) MIDIClientRef midi_client;
@property (nonatomic) MIDIEndpointRef outSrc;
@property (nonatomic) MIDIEndpointRef inSrc;
- (id)initWithVirtualSourceName:(NSString *)clientName;
@end
在你的 .m 文件中
@interface YourVirtualMidiHandlerObject () {
MIDITimeStamp midiTime;
MIDIPacketList pktList;
}
@end
您可以按照以下方式准备虚拟客户端的初始化,同时在您的.m文件中进行操作。
@implementation YourVirtualMidiHandlerObject
-(void)teardown {
MIDIEndpointDispose(_inSrc);
MIDIEndpointDispose(_outSrc);
MIDIClientDispose(_midi_client);
}
- (id)initWithVirtualSourceName:(NSString *)clientName {
if (self = [super init]) {
OSStatus status = MIDIClientCreate((__bridge CFStringRef)clientName, (MIDINotifyProc)MidiNotifyProc, (__bridge void *)(self), &_midi_client);
BOOL isSourceLoaded = NO;
BOOL isDestinationLoaded = NO;
ItemCount sourceCount = MIDIGetNumberOfSources();
for (ItemCount i = 0; i < sourceCount; ++i) {
_outSrc = MIDIGetSource(i);
if ( _outSrc != 0 ) {
if ([[self getMidiDisplayName:_outSrc] isEqualToString:clientName] && !isSourceLoaded) {
isSourceLoaded = YES;
break;
}
}
}
ItemCount destinationCount = MIDIGetNumberOfDestinations();
for (ItemCount i = 0; i < destinationCount; ++i) {
_inSrc = MIDIGetDestination(i);
if (_inSrc != 0) {
if ([[self getMidiDisplayName:_inSrc] isEqualToString:clientName] && !isDestinationLoaded) {
isDestinationLoaded = YES;
break;
}
}
}
if(!isSourceLoaded) {
MIDISourceCreate(_midi_client, (__bridge CFStringRef)clientName, &_outSrc);
MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyMaxTransmitChannels, 16);
MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsProgramChanges, 1);
MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsNotes, 1);
isSourceLoaded = YES;
}
if(!isDestinationLoaded) {
MIDIDestinationCreate(_midi_client, (__bridge CFStringRef)clientName, midiRead, (__bridge void *)(self), &_inSrc);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyAdvanceScheduleTimeMuSec, 1);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesClock, 1);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesNotes, 1);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesProgramChanges, 1);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyMaxReceiveChannels, 16);
isDestinationLoaded = YES;
}
if (!isDestinationLoaded || !isSourceLoaded) {
if (status != noErr ) {
NSLog(@"Failed creation of virtual Midi client \"%@\", so disposing the client!",clientName);
MIDIClientDispose(_midi_client);
}
}
}
return self;
}
-(NSString *)getMidiDisplayName:(MIDIObjectRef)obj {
CFStringRef name = nil;
if (noErr != MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &name)) return nil;
return (__bridge NSString *)name;
}
对于那些在创作过程中尝试读取节奏(MIDI传输)并设置虚拟目标属性的人们……
不要忘记时间戳随数据包一起发送,但一个数据包可以包含多个相同类型的命令,甚至包含多个时钟命令。当构建时钟计数器以查找BPM节奏时,您必须考虑计算至少12个命令才能进行计算。如果您只使用其中3个,则实际上测量的是自己的缓冲区读取处理延迟而不是真正的时间戳。
如果MIDI发送方未能正确设置时间戳,则您的读取过程(回调)将处理时间戳……
void midiRead(const MIDIPacketList * pktlist, void * readProcRefCon, void * srcConnRefCon) {
const MIDIPacket *pkt = pktlist->packet;
for ( int index = 0; index < pktlist->numPackets; index++, pkt = MIDIPacketNext(pkt) ) {
MIDITimeStamp timestamp = pkt->timeStamp;
if ( !timestamp ) timestamp = mach_absolute_time();
if ( pkt->length == 0 ) continue;
const Byte *p = &pkt->data[0];
Byte functionalDataGroup = *p & 0xF0;
switch (functionalDataGroup) {
case 0xF : {
}
break;
case ... : {
}
break;
default : break;
}
}
}
不要忘记客户端需要一个回调函数来处理内部通知。
void MidiNotifyProc(const MIDINotification* message, void* refCon) {
if (message->messageID != kMIDIMsgObjectAdded &&
message->messageID != kMIDIMsgObjectRemoved) return;
}
然后你就可以发送MIDI了...
-(void)sendMIDICC:(uint8_t)cc Value:(uint8_t)v ChZeroToFifteen:(uint8_t)ch {
MIDIPacket *packet = MIDIPacketListInit(&pktList);
midiTime = packet->timeStamp;
unsigned char ctrl[3] = { 0xB0 + ch, cc, v };
while (1) {
packet = MIDIPacketListAdd(&pktList, sizeof(pktList), packet, midiTime, sizeof(ctrl), ctrl);
if (packet != NULL) break;
packet = MIDIPacketListInit(&pktList);
}
MIDIReceived(_outSrc, &pktList);
}
MIDIReceived
已接收。让它沉淀下来。即使在虚拟源之前转发MIDI时,这也是糟糕的命名。幸运的是,我们现在有Swift来解决所有这些文档/命名问题。/s - Rad'Val