CoreMidi : logging received midi messages to an NSTextField

4.9k views Asked by At

Sorry, I don't speak English (I'm using Google Translate).

I'm very new to Xcode. I'm trying to write an app that can listen to received midi messages and show them in an NSTextField (just like a midi monitor).

I use CoreMidi and I am able to connect the app to the desired input and receive Midi messages (I can print them using NSLog). How I can output these message (the same ones I can read in NSLog) in an NSTextField?

I set a property, @synthesized it and connected the NSTextField in Interface Builder, but from the midi callback function I can't access it (it says "Undeclared").

Here the code in MyDocument.h

@property (retain,nonatomic) IBOutlet NSTextField *test_messages;

Here the code in MyDocument.m

@synthesize test_messages;

void midiInputCallback (const MIDIPacketList *list, void *procRef, void *srcRef) {   
id POOL = [[NSAutoreleasePool alloc] init];
UInt16 nBytes;
NSString *ric;
const MIDIPacket *packet = &list->packet[0];
for (unsigned int i = 0; i < list->numPackets; i++) {
    nBytes = packet->length;
    UInt16 iByte, size;

    iByte = 0;
    while (iByte < nBytes) {
        size = 0;
        unsigned char status = packet->data[iByte];
        if (status < 0xC0) {
            size = 3;
        } else if (status < 0xE0) {
            size = 2;
        } else if (status < 0xF0) {
            size = 3;
        } else if (status < 0xF3) {
            size = 3;
        } else if (status == 0xF3) {
            size = 2;
        } else {
            size = 1;
        }

        switch (status & 0xF0) {
            case 0x80:
                ric = @"Note Off";
                break;

            case 0x90:
                ric = @"Note On";
                break;

            case 0xA0:
                ric = @"Aftertouch";
                break;

            case 0xB0:
                ric = @"Control change";
                break;

            case 0xC0:
                ric = @"Program Change";
                break;

            case 0xD0:
                ric = @"Channel Pressure";
                break;

            case 0xE0:
                ric = @"Pitch Wheel";
                break;

            default:
                ric = @"Unk";
                break;
        }
        //TEST HERE
        [test_messages setStringValue:@"TEST TEST"]; //THIS GET "test_messages undeclared (first use in this function)"
        iByte += size;
    }
    packet = MIDIPacketNext(packet);
}
[POOL release];
}

int main(int argc, char *argv[]) {
MIDIClientRef midiClient;
MIDIEndpointRef src; 

OSStatus result;


result = MIDIClientCreate(CFSTR("MIDI client"), NULL, NULL, &midiClient);
if (result != noErr) {
    NSLog(@"Errore : %s - %s",
          GetMacOSStatusErrorString(result), 
          GetMacOSStatusCommentString(result));
    return 0;
}

result = MIDIDestinationCreate(midiClient, CFSTR("Porta virtuale"), midiInputCallback, NULL, &src);
if (result != noErr ) {
    NSLog(@"Errore : %s - %s",
          GetMacOSStatusErrorString(result), 
          GetMacOSStatusCommentString(result));
    return 0;   
}

MIDIPortRef inputPort;
result = MIDIInputPortCreate(midiClient, CFSTR("Input"), midiInputCallback, NULL, &inputPort);

ItemCount numOfDevices = MIDIGetNumberOfDevices();

for (int i = 0; i < numOfDevices; i++) {
    MIDIDeviceRef midiDevice = MIDIGetDevice(i);
    NSDictionary *midiProperties;

    MIDIObjectGetProperties(midiDevice, (CFPropertyListRef *)&midiProperties, YES);
    MIDIEndpointRef src = MIDIGetSource(i);
    MIDIPortConnectSource(inputPort, src, NULL);
}

return NSApplicationMain(argc, (const char **) argv);
}

Thank you in advance for any information that can help me.

1

There are 1 answers

2
Rob Keniger On BEST ANSWER

The main problem that you're having is that you're assuming that the MIDI callback function "knows" about your MyDocument class and is able to access its properties. Unfortunately, that's not the case. C functions have no inherent state information, the only way to pass information to a function is to pass it as an argument.

That's what all the void* refCon arguments are in the documentation. refCon is a generic pointer that you can use to pass a reference to some other object to your function.

For instance, the docs show the signature for the MIDIInputPortCreate() function as follows:

extern OSStatus MIDIInputPortCreate(
    MIDIClientRef client, 
    CFStringRef portName, 
    MIDIReadProc readProc, 
    void *refCon, 
    MIDIPortRef *outPort ); 

In your particular case, you should be passing a reference to your MyDocument object as the refCon parameter. At present you are passing NULL.

MIDIPortRef inputPort;
result = MIDIInputPortCreate(midiClient, 
    CFSTR("Input"), 
    midiInputCallback, 
    myDocument,  //note the change
    &inputPort);

Then, in your callback, you can access the document object and therefore its properties:

void midiInputCallback (const MIDIPacketList *list, void *procRef, void *srcRef) 
{
    //do some MIDI stuff here

    //get a reference to your document by casting the void* pointer
    MyDocument* myDocument = (MyDocument*)procRef;
    //log the message to the text field
    [myDocument.test_messages setStringValue:@"TEST TEST"];
}

That should be how it works. However, in your code above you seem to have a main() function inside your MyDocument.m file. That is completely and totally incorrect. If you are using a Cocoa document-based app, you should not be altering the main() function at all except in very rare circumstances.

Instead, you should do all of your MIDI setup in the ‑windowControllerDidLoadNib: method of NSDocument, which is called when the document's window is loaded and the outlets are guaranteed to be ready.

Something like this:

@implementation MyDocument
@synthesize test_messages;

void midiInputCallback (const MIDIPacketList *list, void *procRef, void *srcRef) 
{
    //do some MIDI stuff here

    //get a reference to your document by casting the void* pointer
    MyDocument* myDocument = (MyDocument*)procRef;
    //log the message to the text field
    [myDocument.test_messages setStringValue:@"TEST TEST"];
}    

‑ (void)windowControllerDidLoadNib:(NSWindowController*)windowController
{
    //set up midi input
    MIDIClientRef midiClient;
    MIDIEndpointRef src; 

    OSStatus result;

    result = MIDIClientCreate(CFSTR("MIDI client"), NULL, NULL, &midiClient);
    if (result != noErr) {
        NSLog(@"Errore : %s - %s",
              GetMacOSStatusErrorString(result), 
              GetMacOSStatusCommentString(result));
        return 0;
    }

    //note the use of "self" to send the reference to this document object
    result = MIDIDestinationCreate(midiClient, CFSTR("Porta virtuale"), midiInputCallback, self, &src);
    if (result != noErr ) {
        NSLog(@"Errore : %s - %s",
              GetMacOSStatusErrorString(result), 
              GetMacOSStatusCommentString(result));
        return 0;   
    }

    MIDIPortRef inputPort;
    //and again here
    result = MIDIInputPortCreate(midiClient, CFSTR("Input"), midiInputCallback, self, &inputPort);

    ItemCount numOfDevices = MIDIGetNumberOfDevices();

    for (int i = 0; i < numOfDevices; i++) {
        MIDIDeviceRef midiDevice = MIDIGetDevice(i);
        NSDictionary *midiProperties;

        MIDIObjectGetProperties(midiDevice, (CFPropertyListRef *)&midiProperties, YES);
        MIDIEndpointRef src = MIDIGetSource(i);
        MIDIPortConnectSource(inputPort, src, NULL);
    }
}

@end