netzstaub

beatz & funkz

Friday, August 22, 2008

Quick and dirty midi merge under MacOSX

I needed some way to debug communications on my mac (sniff a MIDI connection). I tried to set up a MIDI Merge using MidiPipe, but sadly ran into problems while forwarding MIDI Sysex messages. So here is my quick and dirty workaround using the Carbon MIDI API. You can download the code here: midi-merge.c.

It opens up to input ports and two output ports, and basically forwards everything coming in on the input ports to the output ports. When opening an input port, you can register a read procedure for every data coming in. This read procedure receives a list of packets containing the MIDI data (these packets have to be parsed further if you want to use the MIDI stream). However, I just copy the packets to a new packetlist, and send that list out of the appropriate output port. The code looks like this (starting with the main routine):

int main(int argc, char *argv[]) {
  int c;
  int outputDevice = -1;
  int inputDevice = -1;

  while ((c = getopt(argc, argv, "hlb")) != -1) {
    switch (c) {
    case 'l':
      listOutputMidiDevices();
      exit(0);
      break;

    case 'h':
    default:
      usage();
      exit(0);
      break;
    }
  }

  if ((optind + 2) != argc) {
    usage();
    exit(1);
  }
  outputDevice = atoi(argv[optind]);
  inputDevice = atoi(argv[optind+1]);

  if (outputDevice == -1 || inputDevice == -1) {
    usage();
    exit(1);
  }

  MIDIClientRef client = NULL;
  MIDIClientCreate(CFSTR("MIDI Send"), NULL, NULL, &client);
  MIDIOutputPortCreate(client, CFSTR("Output port"), &gOutPort);
  MIDIOutputPortCreate(client, CFSTR("Output port"), &gOutPort2);
  gDest = MIDIGetDestination(outputDevice);
  gDest2 = MIDIGetDestination(inputDevice);
  MIDIPortRef inPort = NULL;
  MIDIInputPortCreate(client, CFSTR("Input port"), myReadProc, NULL, &inPort);
  MIDIPortRef inPort2 = NULL;
  MIDIInputPortCreate(client, CFSTR("Input port"), myReadProc2, NULL, &inPort2);
  

  MIDIEndpointRef src = MIDIGetSource(inputDevice);
  MIDIPortConnectSource(inPort, src, NULL);
  src = MIDIGetSource(outputDevice);
  MIDIPortConnectSource(inPort2, src, NULL);

  CFRunLoopRef runLoop;
  runLoop = CFRunLoopGetCurrent();

  CFRunLoopRun();

  return 0;
}

After the usual command-line argument parsing (including the option to list the available midi devices, which we’ll get to shortly), we create a MIDI client (a way to register our application with the MIDI framework), and create two output ports. These output ports are used to send data to a destination. I don’t really know one doesn’t suffice, because the actual destination is stored in a separate variable. These variables are gDest and gDest2. We also create two input ports which are then connected to two sources. So the setup is: 2 destinations (gDest, gDest2) coupled to 2 output ports (gOutPort, gOutPort2), 2 sources coupled to 2 input ports (inPort, iNport2. These two input ports are registered to two read procedures (I was lazy here and didn’t want to make some kind of structure to pass to the read procedure). Finally, we enter the normal Carbon Runloop.

The interesting code is thus in the read procedures (I’ll show only one here):

void myReadProc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon) {
  if (gOutPort != NULL && gDest != NULL) {
    MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
    unsigned int j;
    int i;
    for (j = 0; j < pktlist->numPackets; j++) {
      midiSendPacket(packet, gOutPort, gDest);
      packet = MIDIPacketNext(packet);
    }
  }
}

We go through the received packets (in pktlist), and just resend them to the appropriate output port using the function midiSendPacket (which is a crude way to copy packet data into a new list and send it off):

void midiSendPacket(MIDIPacket *packet, MIDIPortRef outport, MIDIEndpointRef dest) {
  struct MIDIPacketList pktlist;
  pktlist.numPackets = 1;
  pktlist.packet[0].timeStamp = 0;
  pktlist.packet[0].length = packet->length;
  int i;
  for (i = 0; i < packet->length; i++) {
    pktlist.packet[0].data[i] = packet->data[i];
  }
  MIDISend(outport, dest, &pktlist); 
}

Finally, here is the code to show the available devices. I use one trick here, and that is to use the device name as the model name, and not the actual port’s model. That’s because I want to show the names I have assigned to the individual devices in Audio Midi Setup (I have like 4 interfaces called “MidiLink” connected most of the time).

void listOutputMidiDevices(void) {
  unsigned long   iNumDevs, i;
  
  /* Get the number of MIDI Out devices in this computer */
  iNumDevs = MIDIGetNumberOfDestinations();

  //  printf("%lu output midi devices found\r\n", iNumDevs);
  
  /* Go through all of those devices, displaying their names */
  for (i = 0; i < iNumDevs; i++) {
    CFStringRef pname, pmanuf, pmodel;
    char name[64], manuf[64], model[64];
    
    MIDIEndpointRef ep = MIDIGetDestination(i);
    MIDIEntityRef ent;
    MIDIDeviceRef dev;
    MIDIEndpointGetEntity(ep, &ent);
    MIDIEntityGetDevice(ent, &dev);
    MIDIObjectGetStringProperty(ep, kMIDIPropertyName, &pname);
    MIDIObjectGetStringProperty(ep, kMIDIPropertyManufacturer, &pmanuf);
    MIDIObjectGetStringProperty(dev, kMIDIPropertyName, &pmodel);

    CFStringGetCString(pname, name, sizeof(name), 0);
    CFStringGetCString(pmanuf, manuf, sizeof(manuf), 0);
    CFStringGetCString(pmodel, model, sizeof(model), 0);
    CFRelease(pname);
    CFRelease(pmanuf);
    CFRelease(pmodel);
    
    printf("%d) %s - %s - %s\n", i, name, manuf, model);
  }  
}

To compile the code, you have to link to the carbon framework. Here is the command-line I use:

gcc -framework CoreAudio -framework CoreMIDI -framework Carbon -o midi-merge midi-merge.c

Enjoy!

posted by manuel at 1:36 pm  

1 Comment »

  1. great job,great code#

    Comment by laptop battery — December 9, 2008 @ 6:49 am

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress