我不清楚时间戳字段是否可以用于在将来调度MIDI事件,即在调用MIDISend之后发生。 以下代码尝试每秒安排10个音符和10个音符关闭。 时间戳应该指定该笔记的笔记在笔记开始后的1/10秒处发生,但是在所有我尝试过的硬件或虚拟目标上,看起来所有事件都在MIDISend被调用时立即发送。 这是一般的行为还是做一些MIDI硬件/做一些虚拟MIDI目标支持使用时间戳值正确安排事件? 这是我的代码:
/* send_midi_port.c * Open connection with destination (rather than setting up MIDI source) and send * timestamped messages */ #include <stdlib.h> #include <stdio.h> #include <CoreMIDI.h> #include <HostTime.h> #include <string.h> #include <CoreServices/CoreServices.h> #include <mach/mach.h> #include <mach/mach_time.h> #include <unistd.h> #define STR_BUFSIZE 200 #define ERR_EXIT(x)\ fprintf(stderr,"Error %s\n",x);\ return -1; typedef void (*sig_t) (int); static volatile int done = 0; static mach_timebase_info_data_t sTimebaseInfo; void int_handle(int signum) { done = 1; } char *CFString_strncpy(char *dest, CFStringRef str, size_t n) { CFStringEncoding encoding = kCFStringEncodingUTF8; if (!CFStringGetCString(str, dest, n, encoding)) { dest = NULL; } return dest; } int CFString_cstr_strncmp(CFStringRef s1, char *s2, size_t n) { char buf[STR_BUFSIZE]; if (CFString_strncpy(buf, s1, STR_BUFSIZE) == NULL) { return -2; } return strncmp(buf,s2,n); } /* Print CFString to stdout */ void CFString_printf(CFStringRef str) { char buffer[STR_BUFSIZE]; CFStringEncoding encoding = kCFStringEncodingUTF8; const char *ptr = CFStringGetCStringPtr(str, encoding); if (ptr == NULL) { if (CFStringGetCString(str, buffer, STR_BUFSIZE, encoding)) { ptr = buffer; } } if (ptr) { printf("%s",ptr); } } /* Get MIDI object's name as CFStringRef */ CFStringRef MIDIObjectRef_get_name(MIDIObjectRef obj) { CFStringRef name = NULL; OSStatus err; err = MIDIObjectGetStringProperty(obj,kMIDIPropertyName,&name); if (err) { return NULL; } return name; } UInt64 nano_to_absolute(UInt64 nano) { return nano * sTimebaseInfo.denom / sTimebaseInfo.numer; } int main(int argc, char **argv) { // Get Timebase info (void) mach_timebase_info(&sTimebaseInfo); char desired_dest_[] = "FastTrack Pro"; char *desired_dest; if (argc < 2) { printf("No device specified, using default output %s.\n", desired_dest_); desired_dest = desired_dest_; } else { desired_dest = argv[1]; } ItemCount n_dests = MIDIGetNumberOfDestinations(); int found = 0; MIDIEndpointRef desired_epr; while (n_dests--) { MIDIEndpointRef dest = MIDIGetDestination(n_dests); CFStringRef name = MIDIObjectRef_get_name(dest); if (CFString_cstr_strncmp(name,desired_dest,strlen(desired_dest)) == 0) { printf(" Name of destination %d: ",(int)n_dests); CFString_printf(name); printf("\n"); found = 1; desired_epr = dest; } } if (!found) { printf("Destination %s not found.\n",desired_dest); return -1; } OSStatus result; MIDIClientRef clientref; result = MIDIClientCreate(CFSTR("default"),NULL,NULL,&clientref); if (result < 0 ) { ERR_EXIT("Creating client."); } MIDIPortRef portref; result = MIDIOutputPortCreate(clientref,CFSTR("hiports"),&portref); if (result < 0 ) { ERR_EXIT("Creating port."); } signal(SIGINT,int_handle); while (!done) { // With multiple and time stamps ByteCount mpdsize = sizeof(MIDIPacketList)+sizeof(MIDIPacket)*20; char mpdata[mpdsize]; memset(mpdata,0,mpdsize); MIDIPacketList *midipackets; midipackets = (MIDIPacketList*)mpdata; MIDIPacket *mp; mp = MIDIPacketListInit(midipackets); int n; UInt64 timeNano, timeScale; for (n = 0; n < 20; n += 2) { Byte noteOnData[3] = {0x90,60+(n/2),100}; Byte noteOffData[3] = {0x80,60+(n/2),0}; // on all the devices I tried, the timestamps seem to be ignored mp = MIDIPacketListAdd(midipackets,mpdsize,mp, nano_to_absolute(((UInt64)n/2) * 100ULL * 1000ULL * 1000ULL), // seems equivalent to 0 // 0, 3,noteOnData); if (!mp) { ERR_EXIT("Adding MIDI packet.\n"); } mp = MIDIPacketListAdd(midipackets,mpdsize,mp, nano_to_absolute((((UInt64)n/2)+1) * 100ULL * 1000ULL * 1000ULL), // seems equivalent to 0 // 0, 3,noteOffData); if (!mp) { ERR_EXIT("Adding MIDI packet.\n"); } } // Check packets printf("Number of packets: %d\n",(int)midipackets->numPackets); MIDIPacket *__p = &midipackets->packet[0]; int i; for (i = 0; i < midipackets->numPackets; ++i) { printf("Timestamp: %llu\n" "Length: %d\n" "Data : ", __p->timeStamp, (int)__p->length); int j; for (j = 0; j < __p->length; j++) { printf("%d ",(int)__p->data[j]); } printf("\n"); __p = MIDIPacketNext(__p); } printf("Sending notes\n"); MIDISend(portref,desired_epr,midipackets); sleep(1); } MIDIPortDispose(portref); MIDIClientDispose(clientref); return 0; }这可以使用该命令来构建(您的计算机上的框架路径可能不同)
clang send_midi_port.c -o send_midi_port.bin - I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/CoreMIDI.framework/Headers -framework CoreMIDI -g -framework CoreFoundation -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/CoreAudio.framework/Headers -framework CoreAudio -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/CoreServices.framework/Headers -framework CoreServicesIt is unclear to me whether the timestamp field can be used to schedule MIDI events in the future, i.e., to happen after the call to MIDISend. The following code attempts to schedule 10 note ons and 10 note offs every second. The timestamps should specify that the note's note off happen 1/10th of a second after the the note on, however on all the hardware or virtual destinations I have tried, it seems all the events are sent immediately when MIDISend is called. Is this the general behaviour or does some MIDI hardware / do some virtual MIDI destinations support the the proper scheduling of events using the timestamp value? Here is my code:
/* send_midi_port.c * Open connection with destination (rather than setting up MIDI source) and send * timestamped messages */ #include <stdlib.h> #include <stdio.h> #include <CoreMIDI.h> #include <HostTime.h> #include <string.h> #include <CoreServices/CoreServices.h> #include <mach/mach.h> #include <mach/mach_time.h> #include <unistd.h> #define STR_BUFSIZE 200 #define ERR_EXIT(x)\ fprintf(stderr,"Error %s\n",x);\ return -1; typedef void (*sig_t) (int); static volatile int done = 0; static mach_timebase_info_data_t sTimebaseInfo; void int_handle(int signum) { done = 1; } char *CFString_strncpy(char *dest, CFStringRef str, size_t n) { CFStringEncoding encoding = kCFStringEncodingUTF8; if (!CFStringGetCString(str, dest, n, encoding)) { dest = NULL; } return dest; } int CFString_cstr_strncmp(CFStringRef s1, char *s2, size_t n) { char buf[STR_BUFSIZE]; if (CFString_strncpy(buf, s1, STR_BUFSIZE) == NULL) { return -2; } return strncmp(buf,s2,n); } /* Print CFString to stdout */ void CFString_printf(CFStringRef str) { char buffer[STR_BUFSIZE]; CFStringEncoding encoding = kCFStringEncodingUTF8; const char *ptr = CFStringGetCStringPtr(str, encoding); if (ptr == NULL) { if (CFStringGetCString(str, buffer, STR_BUFSIZE, encoding)) { ptr = buffer; } } if (ptr) { printf("%s",ptr); } } /* Get MIDI object's name as CFStringRef */ CFStringRef MIDIObjectRef_get_name(MIDIObjectRef obj) { CFStringRef name = NULL; OSStatus err; err = MIDIObjectGetStringProperty(obj,kMIDIPropertyName,&name); if (err) { return NULL; } return name; } UInt64 nano_to_absolute(UInt64 nano) { return nano * sTimebaseInfo.denom / sTimebaseInfo.numer; } int main(int argc, char **argv) { // Get Timebase info (void) mach_timebase_info(&sTimebaseInfo); char desired_dest_[] = "FastTrack Pro"; char *desired_dest; if (argc < 2) { printf("No device specified, using default output %s.\n", desired_dest_); desired_dest = desired_dest_; } else { desired_dest = argv[1]; } ItemCount n_dests = MIDIGetNumberOfDestinations(); int found = 0; MIDIEndpointRef desired_epr; while (n_dests--) { MIDIEndpointRef dest = MIDIGetDestination(n_dests); CFStringRef name = MIDIObjectRef_get_name(dest); if (CFString_cstr_strncmp(name,desired_dest,strlen(desired_dest)) == 0) { printf(" Name of destination %d: ",(int)n_dests); CFString_printf(name); printf("\n"); found = 1; desired_epr = dest; } } if (!found) { printf("Destination %s not found.\n",desired_dest); return -1; } OSStatus result; MIDIClientRef clientref; result = MIDIClientCreate(CFSTR("default"),NULL,NULL,&clientref); if (result < 0 ) { ERR_EXIT("Creating client."); } MIDIPortRef portref; result = MIDIOutputPortCreate(clientref,CFSTR("hiports"),&portref); if (result < 0 ) { ERR_EXIT("Creating port."); } signal(SIGINT,int_handle); while (!done) { // With multiple and time stamps ByteCount mpdsize = sizeof(MIDIPacketList)+sizeof(MIDIPacket)*20; char mpdata[mpdsize]; memset(mpdata,0,mpdsize); MIDIPacketList *midipackets; midipackets = (MIDIPacketList*)mpdata; MIDIPacket *mp; mp = MIDIPacketListInit(midipackets); int n; UInt64 timeNano, timeScale; for (n = 0; n < 20; n += 2) { Byte noteOnData[3] = {0x90,60+(n/2),100}; Byte noteOffData[3] = {0x80,60+(n/2),0}; // on all the devices I tried, the timestamps seem to be ignored mp = MIDIPacketListAdd(midipackets,mpdsize,mp, nano_to_absolute(((UInt64)n/2) * 100ULL * 1000ULL * 1000ULL), // seems equivalent to 0 // 0, 3,noteOnData); if (!mp) { ERR_EXIT("Adding MIDI packet.\n"); } mp = MIDIPacketListAdd(midipackets,mpdsize,mp, nano_to_absolute((((UInt64)n/2)+1) * 100ULL * 1000ULL * 1000ULL), // seems equivalent to 0 // 0, 3,noteOffData); if (!mp) { ERR_EXIT("Adding MIDI packet.\n"); } } // Check packets printf("Number of packets: %d\n",(int)midipackets->numPackets); MIDIPacket *__p = &midipackets->packet[0]; int i; for (i = 0; i < midipackets->numPackets; ++i) { printf("Timestamp: %llu\n" "Length: %d\n" "Data : ", __p->timeStamp, (int)__p->length); int j; for (j = 0; j < __p->length; j++) { printf("%d ",(int)__p->data[j]); } printf("\n"); __p = MIDIPacketNext(__p); } printf("Sending notes\n"); MIDISend(portref,desired_epr,midipackets); sleep(1); } MIDIPortDispose(portref); MIDIClientDispose(clientref); return 0; }This can be built with the command (the paths to the frameworks might be different on your machine)
clang send_midi_port.c -o send_midi_port.bin - I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/CoreMIDI.framework/Headers -framework CoreMIDI -g -framework CoreFoundation -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/CoreAudio.framework/Headers -framework CoreAudio -I/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk/System/Library/Frameworks/CoreServices.framework/Headers -framework CoreServices最满意答案
当您调用MIDIPacketListAdd ,您没有提供正确的时间戳。 您正在从当前时间传递一个偏移量,从第一个事件的0开始,并随着后续事件的增加而增加。 但是你不包括当前时间。
因此,CoreMIDI认为时间戳过去了,所以它立即发送事件。
使用AudioGetCurrentHostTime获取当前时间,并将每个偏移添加到该时间。
UInt64 now = AudioGetCurrentHostTime(); for (n = 0; n < 20; n += 2) { Byte noteOnData[3] = {0x90,60+(n/2),100}; Byte noteOffData[3] = {0x80,60+(n/2),0}; mp = MIDIPacketListAdd(midipackets,mpdsize,mp, now + nano_to_absolute(((UInt64)n/2) * 100ULL * 1000ULL * 1000ULL), 3,noteOnData); mp = MIDIPacketListAdd(midipackets,mpdsize,mp, now + nano_to_absolute((((UInt64)n/2)+1) * 100ULL * 1000ULL * 1000ULL), 3,noteOffData); }当您使用MIDISend ,无论最终设备是MIDI硬件还是其他应用程序拥有的虚拟MIDI目标,CoreMIDI都会处理调度。
When you call MIDIPacketListAdd, you are not supplying a correct timestamp. You are passing in an offset from the current time, starting at 0 for the first event, and increasing with later events. But you are not including the current time.
As a result, CoreMIDI thinks the timestamps are in the past, so it sends the events immediately.
Use AudioGetCurrentHostTime to get the current time, and add each offset to that.
UInt64 now = AudioGetCurrentHostTime(); for (n = 0; n < 20; n += 2) { Byte noteOnData[3] = {0x90,60+(n/2),100}; Byte noteOffData[3] = {0x80,60+(n/2),0}; mp = MIDIPacketListAdd(midipackets,mpdsize,mp, now + nano_to_absolute(((UInt64)n/2) * 100ULL * 1000ULL * 1000ULL), 3,noteOnData); mp = MIDIPacketListAdd(midipackets,mpdsize,mp, now + nano_to_absolute((((UInt64)n/2)+1) * 100ULL * 1000ULL * 1000ULL), 3,noteOffData); }When you use MIDISend, CoreMIDI will handle the scheduling, regardless of whether the end device is MIDI hardware, or a virtual MIDI destination owned by some other app.
更多推荐
发布评论