Sometimes it's nice to set variables in MATLAB in real-time, using an actual physical knob. Like you can find on a MIDI controller.
MIDI controller hooked up to my Mac |
The helper program does very little: listen to the MIDI bus, if a controller message is seen, write it to a file in the /tmp directory. In particular, I name the files /tmp/cc/<number>, where <number> is the controller number. Since controllers can only take values 0–127, the files are one byte long.
The MATLAB side is equally simple. If you want to read controller value x, open /tmp/cc/x and read the first byte. Should the read fail, return
function r = getCCval( n ) fname = sprintf( '/tmp/cc/%d', n ); f = fopen( fname, 'r' ); if f<0 r = -1; return end r = fread( f, 1, 'uint8' ); fclose(f); if isempty(r) r = -1; end end
The race condition (MATLAB reading the file while it's being written) also returns
The helper program
The actual reading of MIDI messages is messy, platform-dependent code that I'd rather not be writing myself. So instead I use the rtmidi package, developed just on the other side of the McGill campus; it works on Mac, Linux, and Windows. The helper program is a simple modification of the program qmidiin.cpp from the tests directory in the rtmidi distribution.
//*****************************************// // midicctmp.cpp by Joachim Thiemann 2011 // modified from // qmidiin.cpp by Gary Scavone, 2003-2004. // // read MIDI queue and create files in // /tmp/cc with controller state // //*****************************************// #include#include #include #include "RtMidi.h" #include #define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) ) #include bool done; static void finish( int ignore ){ done = true; } void usage( void ) { // Error function in case of incorrect command-line // argument specifications. std::cout << "\nusage: midicctmp \n"; std::cout << " where port = the device to use (default = 0).\n\n"; exit( 0 ); } int main( int argc, char *argv[] ) { RtMidiIn *midiin = 0; std::vector message; int nBytes; double stamp; FILE *f; // Minimal command-line check. if ( argc > 2 ) usage(); // RtMidiIn constructor try { midiin = new RtMidiIn(); } catch ( RtError &error ) { error.printMessage(); exit( EXIT_FAILURE ); } // Check available ports vs. specified. unsigned int port = 0; unsigned int nPorts = midiin->getPortCount(); if ( argc == 2 ) port = (unsigned int) atoi( argv[1] ); if ( port >= nPorts ) { delete midiin; std::cout << "Invalid port specifier!\n"; usage(); } try { midiin->openPort( port ); } catch ( RtError &error ) { error.printMessage(); goto cleanup; } // Ignore sysex, timing, or active sensing messages. midiin->ignoreTypes( true, true, true ); // Install an interrupt handler function. done = false; (void) signal(SIGINT, finish); // Periodically check input queue. std::cout << "Reading MIDI from port ... quit with Ctrl-C.\n"; while ( !done ) { stamp = midiin->getMessage( &message ); nBytes = message.size(); if (nBytes==3) { char fn[20]; if ((message[0]>>4)==11) { sprintf(fn,"/tmp/cc/%d",(int)message[1]); f = fopen(fn,"w"); fwrite(&message[2],1,1,f); fclose(f); } } // If queue empty, sleep for 10 milliseconds. if (nBytes==0) SLEEP( 10 ); } // Clean up cleanup: delete midiin; return 0; }
The code can be compiled the same way the other program in the tests directory are compiled, on my Mac it was g++ -O3 -Wall -I.. -D__MACOSX_CORE__ -o midicctmp midicctmp.cpp Release/RtMidi.o -framework CoreMIDI -framework CoreFoundation -framework CoreAudio. Note that the program will fail if the directory /tmp/cc does not exists; just create it manually, or modify the above code to create it at startup.
I have not yet tested it on Linux, but I see no reason for it to not work. I have an idea for a slightly more fancy event-based method, but that's for another post.
No comments:
Post a Comment