Virtual loopback MIDI port with CoreMIDI

451 Views Asked by At

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.

2

There are 2 best solutions below

0
Maxim On BEST ANSWER

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.

1
Bradley Ross On

First thing is don't use MIDIReadProc. It is not only deprecated and not supported but has problems. See https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/FunctionalArea.html and https://bradleyross.github.io/ObjectiveC-Examples/Documentation/BRossTools/CoreMidi.html.

A client is a virtual MIDI device that can be a controller, sequencer, synthesizer, etc. Input and output ports are parts of the client to which sources and destinations are attached. When you attach a MIDI device with a USB connection, sources and destinations will be automatically created for those devices.

You can also attach virtual sources and destinations for clients using the MIDIDestinationCreate and MIDISourceCreate (with appropriate suffixes). So a loopback would probably involve a client with an output port and a virtual destination. The output port would then be attached to the virtual destination. The other choice with be to have an input port and virtual source, with the source connected to the input port.

I'm still trying to understand this myself.