BasicMIDIControl
MIDI controls respond to continuous controller or pitch bend messages from your keyboard. BasicMIDIControl lets you assign a function to be executed on receipt of a controller message. More complex controllers can be written using the instructions below.
Like MIDI sockets, you may have an unlimited number of control objects responding to the same MIDI controller on the same channel.
*new(channel, control_num, func)
Channel: the port and channel this socket should listen to. It may be specified several ways:
chan_num (simple integer): this number is the channel number; the port will be assumed 0
\omni: respond to any channel on port 0
nil: assume port 0, channel 0
[port_num, chan_num]: specify a port as well as channel. port_num can be the uid belonging to the port (see MIDIClient and MIDIEndPoint), or an integer index to the sources initialized by MIDIClient.
[port_num, \omni]: respond to any channel on this port
control_num: An integer representing the controller number or an instance of CControl. You may also specify nil, in which case a free controller will be obtained from this channel's CCAllocator.
func: The function to execute, which will receive the controller value as an argument.
port_num here refers to an index into the MIDIPort.sources array, e.g., on my machine:
MIDIPort.init;
Sources: [ FastLane USB #2 : Port A, FastLane USB #2 : Port B, UltraLite : Midi Port ]
Destinations: [ FastLane USB #2 : Port A, FastLane USB #2 : Port B, UltraLite : Midi Port ]
MIDIPort
In this configuration,
port 0 = FastLane USB #2 : Port A
port 1 = FastLane USB #2 : Port B
port 2 = UltraLite : Midi Port
free
Deactivate the control and remove it from the hierarchy.
Example:
m = BasicMIDIControl(\omni, 1, { arg val; ["mw", val].postln }); // 1 = mod wheel
p = BasicMIDIControl(\omni, \pb, { arg val; ["\tpb", val].postln }); // \pb = pitch bend
// move the controllers on your keyboard
// when done:
m.free;
p.free; // now the controllers do nothing
Writing your own controllers
All MIDI controllers should be subclasses of AbstractMIDIControl. The abstract class handles the details of initializing the MIDI channel and placing the controller into the hierarchy. The *new message is always in the format: new(channel, ccnum, destination, args). *new should not be overridden in your subclass.
Any further initialization should be done in the method init. Init will receive any arguments after the channel number and destination given in the new call. For example, in BasicMIDIControl:
init {
func = destination; // since I'm the destination (see below), I can use the
destination = this; // destination variable to get my func, then set dest. to me
}
In this case, the controller is its own destination, so I do some swapping of variables to make the syntax cleaner. If you have a different destination, leave the destination variable alone.
AbstractMIDISocket-free removes the object from the hierarchy, then calls the clear method.
clear {
func = nil;
}
The instance variable destination holds the object being played by the socket. It is set to the second argument to new() before your init method is called.
The destination must respond to active with a Boolean which is true if the object being played is still in action and false if that object has been freed. When MIDIPort.update is called, it scans all the objects in the hierarchy and if .destination.active is false, that responder is freed. In the case of VoicerMIDISocket, which can create several MIDI controller objects in addition to the MIDI socket, the voicer (destination) calls MIDIPort.update when it's freed, and Voicer's active method tells MIDIPort that its MIDI objects can disappear. Your player should do this as well.
If the destination is this (as above), then your control must implement active. From BasicMIDIControl:
active { ^onPlayer.notNil } // since I'm the destination, I must say if I'm active or not
Most controllers will use a ControlSpec to map the incoming value (divided by 127) onto another range. The spec method should return this spec. The easiest way is to have an instance variable with a getter: var <spec;
For some gui purposes, you might want to give your controller a name. The name method should return this. If you don't supply a name, the object.asString is the default.
Finally, the socket must implement set, which is the controller's action. It takes the controller's value as its argument. From BasicMIDIControl:
set { arg value; func.value(value) }
BasicMIDIControl in its entirety:
BasicMIDIControl : AbstractMIDIControl {
var <>func;
init {
func = destination; // see BasicMIDISocket for an explanation of this trick
destination = this;
}
clear {
func = nil;
}
active { ^func.notNil } // since I'm the destination, I must say if I'm active or not
spec { ^[0, 1].asSpec }
set { arg value; func.value(value) }
}