ASCIIMath creating images

Showing posts with label MIDI. Show all posts
Showing posts with label MIDI. Show all posts

Friday, November 28, 2014

The new and improved method of real-time MIDI control of MATLAB (or Python, or ... whatever!)

In an older post on my blog, I had this method to get the state of a MIDI controller into MATLAB (though readable by pretty much anything that can read files).  This method is quite clunky and when I wanted to do something similar again, I re-thought the problem and came up with a better method based on memory mapped files. You can find the code on GitHub.

Basically, I'm creating a file of fixed length (260 bytes, in /tmp), mmap() it and update the contents based on the MIDI stream I'm receiving.  The first 128 bytes are for keys, where each byte is the last seen velocity (0 means "off").  The following 128 bytes are for the cc messages.  Bytes 257 and 258 store the pitch bend, a 14-bit value with MSB in byte 257.  Byte 259 is the last received program change value and 260 is the channel aftertouch.

MATLAB Access

Accessing the values from MATLAB does not require any special functions, it can simply be done using

mm = memmapfile('/tmp/midibroadcast');

then the data can be obtained in real-time from mm.Data.  As described above, mm.Data(1:128) are the last seen key down velocities (0 meaning key is not pressed) and mm.Data(129:256) are controllers 0..127.  The pitch bend value can be obtained as mm.Data(257)*256+mm.Data(258) (note this may be buggy; my cheap controller (KeyRig 25) does not let me set and hold precise pitch bends, so I can't test it properly). mm.Data(259) is the program change and mm.Data(260) shows the current aftertouch amount.

Python Access

From Python the values can be accessed as easily:

import os
import mmap
mfd = os.open('/tmp/midibroadcast', os.O_RDONLY)
mfile = mmap.mmap(mfd, 0, prot=mmap.PROT_READ)

Remembering that Python counts from 0, the controllers can be read in real-time as mfile[128] to mfile[255]: just subtract 1 from the descriptions above.

Any language which can do memory-mapping should be able to do the same, but it should even be possible to read the current state just by re-reading the /tmp/midibroadcast file.

Enjoy!

Sunday, September 11, 2011

Real-time control of MATLAB using a MIDI controller

UPDATE 28/11/2014: this post is obsolete, I have created a better method and described it here.

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
MATLAB generally is not geared for real-time data input, and to the best of my knowledge, there is no MIDI library for input.  So, I hacked up a shockingly simple way of getting controller status data into MATLAB: a small helper program passing data through the filesystem.

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 -1,: let the calling program figure out what to do in that case (eg. read again or use the old value).  The code is simply:

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 -1, given the single-byte length of the file that is sufficient.  Again, the calling program should handle it somehow.

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.