Why doesn't this simple CoreMIDI program produce MIDI output?

后端 未结 4 519
独厮守ぢ
独厮守ぢ 2020-12-23 23:56

Here is an extremely simple CoreMIDI OS X application that sends MIDI data. The problem is that it doesn\'t work. It compiles fine, and runs. It reports no errors, and does

相关标签:
4条回答
  • 2020-12-24 00:35

    A little detail that others are skipping: the time parameter of MIDIPacketListAdd is important for some musical apps.

    Here is an example of how you can retrieve it:

    #import <mach/mach_time.h>
    MIDITimeStamp midiTime = mach_absolute_time();
    

    Source: Apple Documentation

    And then, applied to the other examples here:

    pktBuffer[1024];
    MIDIPacketList *pktList = (MIDIPacketList*)pktBuffer;
    MIDIPacket *pktPtr = MIDIPacketListInit(pktList);
    MIDITimeStamp midiTime = mach_absolute_time();
    Byte midiDataToSend[] = {0x91, 0x3c, 0x40};
    pktPtr = MIDIPacketListAdd(pktList, sizeof(pktList), pktPtr, midiTime, sizeof(midiDataToSend), midiDataToSend);
    
    0 讨论(0)
  • 2020-12-24 00:42

    You're calling MIDISourceCreate to create a virtual MIDI source.

    This means that your source will appear in other apps' MIDI setup UI, and that those apps can choose whether or not to listen to your source. Your MIDI will not get sent to any physical MIDI ports, unless some other app happens to channel it there. It also means that your app has no choice as to where the MIDI it's sending goes. I'm assuming that's what you want.

    The documentation for MIDISourceCreate says:

    After creating a virtual source, use MIDIReceived to transmit MIDI messages from your virtual source to any clients connected to the virtual source.

    So, do two things:

    • Remove the code that creates the output port. You don't need it.
    • change MIDISend(outPort, midiOut, pktList) to: MIDIReceived(midiOut, pktlist).

    That should solve your problem.

    So what are output ports good for? If you wanted to direct your MIDI data to a specific destination -- maybe a physical MIDI port -- you would NOT create a virtual MIDI source. Instead:

    1. Call MIDIOutputPortCreate() to make an output port
    2. Use MIDIGetNumberOfDestinations() and MIDIGetDestination() to get the list of destinations and find the one you're interested in.
    3. To send MIDI to one destination, call MIDISend(outputPort, destination, packetList).
    0 讨论(0)
  • 2020-12-24 00:42

    I'm just leaving this here for my own reference. It's a full example based 100% on yours, but including the other side (receiving), my bad C code and the accepted answer's corrections (of course).

    #import "AppDelegate.h"
    
    @implementation AppDelegate
    
    @synthesize window = _window;
    
    #define NSLogError(c,str) do{if (c) NSLog(@"Error (%@): %u:%@", str, (unsigned int)c,[NSError errorWithDomain:NSMachErrorDomain code:c userInfo:nil]); }while(false)
    
    static void spit(Byte* values, int length, BOOL useHex) {
        NSMutableString *thing = [@"" mutableCopy];
        for (int i=0; i<length; i++) {
            if (useHex)
                [thing appendFormat:@"0x%X ", values[i]];
            else
                [thing appendFormat:@"%d ", values[i]];
        }
        NSLog(@"Length=%d %@", length, thing);
    }
    
    - (void) startSending {
        MIDIEndpointRef midiOut;
        char pktBuffer[1024];
        MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer;
        MIDIPacket     *pkt;
        Byte            midiDataToSend[] = {0x91, 0x3c, 0x40};
        int             i;
    
        MIDISourceCreate(theMidiClient, CFSTR("Magical MIDI Source"),
                         &midiOut);
        pkt = MIDIPacketListInit(pktList);
        pkt = MIDIPacketListAdd(pktList, 1024, pkt, 0, 3, midiDataToSend);
    
        for (i = 0; i < 100; i++) {
            if (pkt == NULL || MIDIReceived(midiOut, pktList)) {
                printf("failed to send the midi.\n");
            } else {
                printf("sent!\n");
            }
            sleep(1);
        }
    }
    
    void ReadProc(const MIDIPacketList *packetList, void *readProcRefCon, void *srcConnRefCon)
    {
        const MIDIPacket *packet = &packetList->packet[0];
    
        for (int i = 0; i < packetList->numPackets; i++)
        {
    
            NSData *data = [NSData dataWithBytes:packet->data length:packet->length];
            spit((Byte*)data.bytes, data.length, YES);
    
            packet = MIDIPacketNext(packet);
        }
    }
    
    - (void) setupReceiver {
        OSStatus s;
        MIDIEndpointRef virtualInTemp;
        NSString *inName = [NSString stringWithFormat:@"Magical MIDI Destination"];
        s = MIDIDestinationCreate(theMidiClient, (__bridge CFStringRef)inName, ReadProc,  (__bridge void *)self, &virtualInTemp);
        NSLogError(s, @"Create virtual MIDI in");
    }
    
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
        MIDIClientCreate(CFSTR("Magical MIDI"), NULL, NULL,
                         &theMidiClient);
        [self setupReceiver];
        [self startSending];
    
    }
    
    @end
    
    0 讨论(0)
  • Consider your own midi client creating application may crash or the host sending midi can crash also. You can handle this easier with checking if an client/destination exists already then doing this by handling singleton allocations. When your Midi client is existing but not working then this is because you need to tell CoreMidi what your costume made client is capable of processing and what latency it will have specially when the host sending client is using timestamps a lot (aka ableton and other).

    in your .h file

    #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
    

    in your .m file

    @interface YourVirtualMidiHandlerObject () {
       MIDITimeStamp midiTime;
       MIDIPacketList pktList;
    }
    @end
    

    You would prepare initiation of your virtual client in the following way also in your .m file

    @implementation YourVirtualMidiHandlerObject 
    
    // this you can call in dealloc or manually 
    // else where when you stop working with your virtual client
    -(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; //stop looping thru sources if it is existing
                    }
                }
            }
    
            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; //stop looping thru destinations if it is existing
                    }
                }
            }
    
            if(!isSourceLoaded) {
                //your costume source needs to tell CoreMidi what it is handling
                MIDISourceCreate(_midi_client, (__bridge CFStringRef)clientName, &_outSrc);
                MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyMaxTransmitChannels, 16);
                MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsProgramChanges, 1);
                MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsNotes, 1);
                // MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsClock, 1);
                isSourceLoaded = YES;
            }
    
            if(!isDestinationLoaded) {
                //your costume destination needs to tell CoreMidi what it is handling
                MIDIDestinationCreate(_midi_client, (__bridge CFStringRef)clientName, midiRead, (__bridge void *)(self), &_inSrc);
                MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyAdvanceScheduleTimeMuSec, 1); // consider more 14ms in some cases
                MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesClock, 1);
                MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesNotes, 1);
                MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesProgramChanges, 1);
                MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyMaxReceiveChannels, 16);
                // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesMTC, 1);
                // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesBankSelectMSB, 1);
                // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesBankSelectLSB, 1);
                // MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertySupportsMMC, 1);
                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;
    }
    
    // Returns the display name of a given MIDIObjectRef as an NSString
    -(NSString *)getMidiDisplayName:(MIDIObjectRef)obj {
        CFStringRef name = nil;
        if (noErr != MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &name)) return nil;
        return (__bridge NSString *)name;
    }
    

    For those of you trying to read tempo (midi transport) and set the propertys for the virtual destination in your creation process... Don't forget timestamps are send with the packets but a packet can contain several commands of same type, even several clock commands. When constructing a clock counter to find bpm tempo you will have to consider counting at least 12 of them before calculating. When you go only with 3 of them you are actually measuring your own buffer read processing latency instead of the real timestamps. Your reading procedure (callback) will handle timestamps if the midi sender fails to set those properly with...

    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;
    
            // Analyse the buffered bytes in functional groups is faster
            // like 0xF will tell it is clock/transport midi stuff
            // go in detail after this will shorten the processing
            // and it is easier to read in code
            switch (functionalDataGroup) {
                case 0xF : {
                       // in here read the exact Clock command
                       // 0xF8 = clock
                   }
                   break;
                case ... : {
                       // do other nice grouped stuff here, like reading notes
                   }
                   break;
                default  : break;
            }
        }
    }
    

    dont forget the client needs a callback where internal notifications are handled.

    void MidiNotifyProc(const MIDINotification* message, void* refCon) {
    
        // when creation of virtual client fails we despose the whole client
        // meaning unless you need it you can ignore added/removed notifications
        if (message->messageID != kMIDIMsgObjectAdded &&
            message->messageID != kMIDIMsgObjectRemoved) return;
    
        // reactions to other midi notications you gonna trigger here..
    }
    

    then you can send midi with...

    -(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;
            // create an extra packet to fill when it failed before
            packet = MIDIPacketListInit(&pktList);
        }
    
        // OSStatus check = // you dont need it if you don't check failing
        MIDIReceived(_outSrc, &pktList);
    }
    
    0 讨论(0)
提交回复
热议问题