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.
If you grabbed a binary package, umidi is allready built and you can skip this
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.
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.
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.
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.
Simply put for each robot a line in the configuration of the form:
robot <id> <host> <port>
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.
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:
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.
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, , "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, , "DirectMap", ["headPan.val",0,1,-64,-1], 1);
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, , "DirectMap", ["midi.velocity", 1, 1/127, 0, -1, 1], 65535);
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, , "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 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, , "DirectMap", ["midi.vel", 1, 1/127, 0, -1, 1], 1); midi.registerAction(1, , "TagMap", ["midi.tag"], 1);
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.
// Set parameter bit 0 based on switch 0 0 midi.registerAction(65536, , "ParameterSet", , 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);