Virtual loopback MIDI port with CoreMIDI

I'm trying to write simple loopback virtual MIDI port on macOS using CoreMIDI in C. First of all, it seems I can't understand all the CoreMIDI terminology. Look please at the following picture:

┌─────────────┐
│ MIDI DEVICE │
│             │
└─OUT──────IN─┘
  ↓         ↑
event     event
  ↓         ↑
──A─────────B──  application
  ↓         ↑

So we have a MIDI device. This device has out port and in port from the device point of view. So via out port MIDI device sends MIDI data and via in port it receives data.

But now let's look on this system from an application point of view. For application MIDI data that is going from a MIDI device, is actually received (point A on the picture above) by app from the device out port. And data that is going from the app, is received by device via its in port from app (point B).

So my first question is what API represents A and B? There is four concepts in CoreMIDI:

  • source
  • destination
  • input port
  • output port

So if we want to receive MIDI data from a MIDI device, what sould we use? I suppose we need something like that:

void MyReadProc(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon)
{
}

...

MIDIClientRef client;
MIDIClientCreate(CFSTR("CLIENT"), NULL, NULL, &client);
    
MIDIPortRef inPort;
MIDIInputPortCreate(client, CFSTR("TEST"), MyReadProc, NULL, &inPort);
    
MIDIPortConnectSource(inPort, srcEndpoint, NULL);

where srcEndpoint is MIDIEndpointRef representing source.

Much funny things are happen when we want to send MIDI data to a device. Should we use MIDISend or MIDIReceived?

Now I need to show my implementation of loopback port:

#include <stdio.h>
#include <CoreFoundation/CoreFoundation.h>
#include <CoreMIDI/CoreMIDI.h>
#include <mach/mach_time.h>
#include <string.h>

void MyReadProc(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon)
{
    if (readProcRefCon != NULL && srcConnRefCon != NULL)
    {
        MIDIPortRef portRef = *((MIDIPortRef*)readProcRefCon);
        MIDIEndpointRef destRef = *((MIDIEndpointRef*)srcConnRefCon);
        
        OSStatus result = MIDISend(portRef, destRef, pktlist);
        
        printf("B\n");
    }
}

...

MIDIClientRef client;
MIDIClientCreate(CFSTR("CLIENT"), NULL, NULL, &client);
    
MIDIPortRef outPort;
MIDIOutputPortCreate(client, CFSTR("TEST"), &outPort);
    
MIDIEndpointRef destEndpoint;
MIDIDestinationCreate(client, CFSTR("TEST"), MyReadProc, NULL, &destEndpoint);
    
MIDIPortRef inPort;
MIDIInputPortCreate(client, CFSTR("TEST"), MyReadProc, &outPort, &inPort);
    
MIDIEndpointRef srcEndpoint;
MIDISourceCreate(client, CFSTR("TEST"), &srcEndpoint);
    
MIDIPortConnectSource(inPort, srcEndpoint, &destEndpoint);

OK, I can create input MIDI port, connect it to source and receive events. But loopback means that if I send data to TEST out port (via out port, destination, source, MIDISend/Received, something else??), I want immediately receive that data from TEST in port.

And I really don't understand how that loopback system should be built and how user will interact with it?

So should be two ports in the system (or source and destination?). User somehow gets reference to out port (or source or destination?), sends data via it, and gets data back via reference to in port (or source or destination?). Seems like my head blows up.

Accepted Reply

OK, I finally figured out how to build loopback system with CoreMIDI.

  1. Source is an input device from an app point of view
  2. Destination is an output device from an app point of view

So we just need to create destination with callback in which we will notify sources (with MIDIReceived) about new MIDI data arrived. So we provide source reference as an argument for callback in MIDIDestinationCreate and use this source inside the callback.

Replies

OK, I finally figured out how to build loopback system with CoreMIDI.

  1. Source is an input device from an app point of view
  2. Destination is an output device from an app point of view

So we just need to create destination with callback in which we will notify sources (with MIDIReceived) about new MIDI data arrived. So we provide source reference as an argument for callback in MIDIDestinationCreate and use this source inside the callback.