Umidi: MIDI to URBI bridge

Introduction

umidi is a GPLed multiplatform URBI to MIDI bridge. It takes imput from a MIDI source, and outputs URBI commands based on those inputs. It is highly configurable, from a configuration file or directly from URBI.

Main intended use is to program robot dances and other movement sequences using MIDI sequencers on URBI-enabled robots.

Main features

Building

If you grabbed a binary package, umidi is allready built and you can skip this section.
Umidi uses the ALSA sequencer API under Linux, and the Windows MIDI API under Microsof Windows. It depends on the liburbi (also known as urbisdk remote) to build. This library is available under the GPL license at http://www.gostai.com.
Umidi also relies on boost headers.
Under GNU/linux and other POSIX systems, configure make make install should do the trick.

Starting umidi

On Microsoft Windows:

        umidi.exe [midiDevice] [configFile] [debugLevel]

umidi will list the detected midi input devices, and will connect to the first one if midiDevice is not set. Note that Windows MIDI system does not permit two MIDI softwares to connect directly to each other. You can use a tool named "MIDI Yoke" that implements 'loopback' MIDI devices if that's what you want.

On GNU/Linux:

        umidi [configFile] [debugLevel]

umidi will register itself as an ALSA midi input port. You can then connect it to the midi source you want, using qjackctl or any other similar tool.

Configuration file

Umidi parses the file passed as configFile, or 'umidi.conf' if it was not specified on the command line.
This file contain lines that define robots umidi sould connect to, and MIDI-URBI bindings umidi should activate. All lines starting with '#' are considered as comments and ignored.

Adding robots

Simply put for each robot a line in the configuration of the form:

      robot <id> <host> <port>

For instance:

      robot 0 snoopy 54000

<Host> and <port> defines the URBI server to connect to. Id is an unique number from 0 to 31 that will be used to decide which actions applies on which robots. 54000 is the default port used by URBI servers.

umidi will establish one connection with each of the robots listed in the configuration, and send the content of the file "umidi.u" if present.

Default actions

umidi will periodically set the URBI variable 'midi.period' to the MIDI period in milliseconds (that is, 60000 / bpm). 'midi.bpm' is also set accordingly. umidi also emits the URBI event 'midi.tick(0)' at each beat start. Finally, umidi sends a small piece of code in the server that will update the variable 'midi.period' based on the above variables and events. This variables is reset to 0 at each beat start, and goes linearly to 1 in 'midi.period'. Conesquently, the code:

          loop headPan.val=20**sin(midi.period**2**pi),

will make the robot turn its head at the same rate as the MIDI bpm.

Note that for the above to work, the MIDI device connected to umidi must transmit MIDI realtime clock messages.

umidi also defines a functiom midi.setDebug(x) that can be called to set umidi debug level. Output goes to umidi standard error stream.

Registering new actions

Foreword: There are a lot of parameters, but it is not as complex as it looks. You can scroll to the examples to get a first idea of what it looks like, and then come back to the formal description of what all the parameters do.

umidi can be configured to perform URBI actions upon reception of some MIDI events. You can register new actions from any of the connected URBI servers (more precisely from a different connection to one of those servers, using an URBI client such as urbiLab), or from the configuration file. Each action is associated with a filter that specifies on what conditions the action will apply.

The generic syntax from the configuration file is:

      action <channelMask> <Filter> <targetMask> <actionName> <actionParams>

where <Filter> is <pitch>/<pitchMask>=<parameter>/<parameterMask> and the syntax from URBI is a call to midi.registerAction:

      midi.registerAction(chanMask, Filter, actionName, actionParams, targetMask);

where filter is [pitch, pitchMask, parameter, parameterMask].

The parameters have the folowwing meaning:

Some simple examples in both URBI and conf syntax:

Map all channel 1 notes to headPan.val, use the pitch:

      action 1 0 1 DirectMap headPan.val 0 1 -64 -1
      midi.registerAction(1, [0], "DirectMap", ["headPan.val",0,1,-64,-1], 1);

Map midi parameter 2 on channels 1 and 2 to headPan.val

      action 0x30000 1 1 DirectMap headPan.val 1 1.2 -64 -1
      midi.registerAction(65536*3, [0], "DirectMap", ["headPan.val",0,1,-64,-1], 1);

The DirectMap action

This action writes a value to a variable each time a MIDI event matches its filter.

Options: target mapWhat factor offset [ resetValue [ appendPitchToName] ] Where:

      # Fill midi.velocityX with a velocity value of last use of note X on all
      # channel, between 0 and 1
      midi.registerAction(65535, [0], "DirectMap", ["midi.velocity", 1, 1/127,
      0, -1, 1], 65535);

The EmitMap action

The EmitMap action emits an event each time a message is received (and passes the action filters of course).

It takes a single argument: the name of the message to emit. The message is emited with four arguments messageType, channel, pitch, velocity.

Exemple: Just bounce everything to URBI and make the binding using ats.

      // Ok, we have problem transmitting too big numbers for now, so be careful.
      midi.registerAction[65535 + 65536*15, [0], "EmitMap", ["midi.evnt"], 1);
      // Bind controler 0 on channel 0 to headPan.val
      at (midi.evnt(11, 16, 0, x)) headPan.val = (x-64)*80/64;

The TagMap action

The TagMap action freezes and unfreezes urbi tags upon reception of messages. It takes one or two arguments: tagName [appendPitch] tagName is the name of the tag to freeze or unfreeze. If appendPitch is set to 1, then the pitch of the event will be appended to the tag name. The tag is frozen is the event is a note-up or a controler with value 0, unfrozen otherwise.

Exemple: Make headTilt.val move in rythm with the bpm when middle-C key is pressed. Make the movement amplitude depend on key velocity.

      var midi.vel = 0;
      midi.tag:loop  headTilt.val = 20 + 20*midi.vel  sin(midi.phasepi/2),
      freeze midi.tag;
      midi.registerAction(1, [64], "DirectMap", ["midi.vel", 1, 1/127, 0,
      -1, 1], 1);
      midi.registerAction(1, [64], "TagMap", ["midi.tag"], 1);

The ParameterSet action

This action is a workaround for MIDI controllers on which changing the mapping of a physical knob or slider is hard: you can configure some buttons in latch mode (ie toogle mode), bind them using the ParameterSet Action, and then make other actions using your knobs as source conditional on the parameter value.

This action has a single parameter: the bitmask to set or unset upon reception of a MIDI event.

Exemple
Suppose you have a MIDI knob that sends message on channel 0 id 0, and a MIDI button in latch mode on channel 0 id 1. You want the knob to control either headPan.val or headTilt.val, depending on whether the button is pressedor not. Here is how to do it, assigning umidi parameter bit 0 for the task:
      // Set parameter bit 0 based on switch 0 0
      midi.registerAction(65536, [1], "ParameterSet", [1], 0);
      // Know make our two directmap dependant on the parameter
      midi.registerAction(65536, [0,255,0,1], "DirectMap",
        ["headPan.val", 1, headPan.val->rangemax / 64, -64], 65535);
      midi.registerAction(65536, [0,255,1,1], "DirectMap",
        ["headTilt.val", 1, headTilt.val->rangemax / 64, -64], 65535);