使用CoreMIDI的MIDISend时是否忽略MIDIPacket时间戳?

时间:2016-11-22 01:08:09

标签: coremidi

我不清楚时间戳字段是否可用于在将来安排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 CoreServices

1 个答案:

答案 0 :(得分:0)

当您致电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时,CoreMIDI将处理调度,无论终端设备是MIDI硬件还是其他应用程序拥有的虚拟MIDI目标。