MIDIIn
This document explains technical details of the MIDI hardware interface class, MIDIIn.
MIDIIn is a simple and direct interface. When MIDI events come into Super Collider, MIDIIn evaluates simple handler functions.
For general programming, users should not use the MIDIIn class directly. See the MIDIResponder classes for higher level event matching and more flexible handling of event handlers.
Certain MIDI messages are supported only through MIDIIn. These are: polytouch, program, sysex, sysrt, smpte.
See the UsingMIDI helpfile for practical considerations and techniques for using MIDI in SC.
The MIDIIn class
MIDIIn links MIDI input received from the operating system to a set of user defined functions.
Only one set of MIDI input handling functions can be active at a time, they are stored in the
following class variables:
noteOff, noteOn, polytouch, control, program, touch, bend, sysex, sysrt, smpte
The first argument these functions receive is an unique identifier that specifies the source of the data.
Quick start for 1 port:
(
MIDIIn.connect; // init for one port midi interface
// register functions:
MIDIIn.noteOff = { arg src, chan, num, vel; [chan,num,vel / 127].postln; };
MIDIIn.noteOn = { arg src, chan, num, vel; [chan,num,vel / 127].postln; };
MIDIIn.polytouch = { arg src, chan, num, vel; [chan,num,vel / 127].postln; };
MIDIIn.control = { arg src, chan, num, val; [chan,num,val].postln; };
MIDIIn.program = { arg src, chan, prog; [chan,prog].postln; };
MIDIIn.touch = { arg src, chan, pressure; [chan,pressure].postln; };
MIDIIn.bend = { arg src, chan, bend; [chan,bend - 8192].postln; };
MIDIIn.sysex = { arg src, sysex; sysex.postln; };
MIDIIn.sysrt = { arg src, chan, val; [chan,val].postln; };
MIDIIn.smpte = { arg src, chan, val; [chan,val].postln; };
)
Quick start for 2 or more ports:
(
var inPorts = 2;
var outPorts = 2;
MIDIClient.init(inPorts,outPorts); // explicitly intialize the client
inPorts.do({ arg i;
MIDIIn.connect(i, MIDIClient.sources.at(i));
});
)
class methods:
*findPort(deviceName,portName)
searches for a connected MIDIEndPoint by name
list connected ins:
MIDIClient.sources
*noteOn_(function)
function is evaluated whenever a MIDI noteOn message is received, it is passed the following arguments:
uid unique identifier of the MIDI port
MIDIchannel ranges from 0 to 15
keyNumber 0 - 127
velocity 0 - 127
*noteOff_(function)
uid unique identifier of the MIDI port
MIDIchannel ranges from 0 to 15
keyNumber 0 - 127
velocity 0 - 127, typically 64 unless noteOff velocity is supported
*polytouch_(function)
uid unique identifier of the MIDI port
MIDIchannel ranges from 0 to 15
keyNumber 0 - 127
pressure 0 - 127
*control_(function)
uid unique identifier of the MIDI port
MIDIchannel ranges from 0 to 15
controllerNumber 0 - 127
value 0 - 127
*program_(function)
uid unique identifier of the MIDI port
MIDIchannel ranges from 0 to 15
programNumber 0 - 127
*touch_(function)
uid unique identifier of the MIDI port
MIDIchannel ranges from 0 to 15
pressure 0 - 127
*bend_(function)
uid unique identifier of the MIDI port
MIDIchannel ranges from 0 to 15
bend 0..16384, the midpoint is 8192
*sysex_(function)
uid unique identifier of the MIDI port
system exclusive data an Int8Array (includes f0 and f7)
see manufacturer references for details
note: The current implementation assembles a complete system exclusive packet
before evaluating the function.
*sysrt_(function)
uid unique identifier of the MIDI port
index ranges from 0 to 15
data 0 - 127
index data message
2 14bits song pointer
3 7bits song select
8 midiclock
10 start
11 continue
12 stop
*smpte
uid unique identifier of the MIDI port
index ranges from 0 to 7
data 4 bits
Over MIDI, SMPTE is transmitted at 1/4 frame intervals four times faster than the frame rate.
index data
0 frames low nibble
1 frames hi nibble
2 seconds low nibble
3 seconds hi nibble
4 minutes low nibble
5 minutes hi nibble
6 hours low nibble
7 hours hi bit OR'ed with frameRate
0 -> 24fps
2 -> 25 fps
4 -> 30 fps drop frame
6 -> 30 fps
Nibbles are sent in ascending order,
(
MIDIIn.connect;
s = Server.local;
s.boot;
s.latency = 0;
SynthDef("sik-goo", { arg freq=440,formfreq=100,gate=0.0,bwfreq=800;
var x;
x = Formant.ar(
SinOsc.kr(0.02, 0, 10, freq),
formfreq,
bwfreq
);
x = EnvGen.kr(Env.adsr, gate,Latch.kr(gate,gate)) * x;
Out.ar(0, x);
}).send(s);
x = Synth("sik-goo");
//set the action:
MIDIIn.noteOn = {arg src, chan, num, vel;
x.set(\freq, num.midicps / 4.0);
x.set(\gate, vel / 200 );
x.set(\formfreq, vel / 127 * 1000);
};
MIDIIn.noteOff = { arg src,chan,num,vel;
x.set(\gate, 0.0);
};
MIDIIn.bend = { arg src,chan,val;
//(val * 0.048828125).postln;
x.set(\bwfreq, val * 0.048828125 );
};
)
//i used this and got acceptable latency for triggering synths live.
//The latency might actually be less than sc2, but i haven't used it enough
//to tell for sure yet.
//Powerbook G4, 512mb ram.
- matrix6k@somahq.com
writing to the bus rather than directly to the synth
s = Server.local;
s.boot;
(
s.latency = 0;
SynthDef("moto-rev", { arg ffreq=100;
var x;
x = RLPF.ar(LFPulse.ar(SinOsc.kr(0.2, 0, 10, 21), [0,0.1], 0.1),
ffreq, 0.1)
.clip2(0.4);
Out.ar(0, x);
}).send(s);
b = Bus.control(s);
x = Synth("moto-rev");
// map the synth's first input (ffreq) to read
// from the bus' output index
x.map(0, b);
MIDIIn.connect;
//set the action:
MIDIIn.noteOn = {arg src, chan, num, vel;
b.value = num.midicps.postln;
};
MIDIIn.control = {arg src, chan, num, val;
[chan,num,val].postln;
};
MIDIIn.bend = {arg src, chan, val;
val.postln;
};
)
// cleanup
x.free;
b.free;
Keyboard Split for two voices
pbend to cutoff, mod to rez, 7 to amp
// - matrix6k@somahq.com
prepare
s.boot;
(
SynthDef("funk",{ arg freq = 700, amp = 0.2, gate = 1, cutoff = 20000, rez = 1, lfospeed=0;
var e,x,env,range,filterfreq;
e = Env.new([0, 0.1, 0.1, 0], [0, 0.1, 0.1], 'linear', 2);
env=Env.adsr(0.3,1,1,1);
range = cutoff -1;
filterfreq = SinOsc.kr(lfospeed,0, range, cutoff).abs;
x = RLPF.ar(Mix.ar([
Mix.arFill(2, {Saw.ar(freq *2 + 0.2.rand2, amp)}),
Mix.arFill(2, {Saw.ar(freq *4+ 0.2.rand2, amp)})
]),
EnvGen.kr(env,gate)*filterfreq,
rez);
Out.ar([0,1],x * EnvGen.kr(e, gate, doneAction: 2))
}).send(s);
SynthDef("strings",{ arg freq = 700, amp = 0.2, gate = 1;
var x,enve;
enve = Env.new([0, 0.1, 0.1, 0], [2, 0.1, 1], 'linear', 2);
x = RLPF.ar(Mix.ar([
Mix.arFill(2, {Saw.ar(freq +2.rand2,0.6)}),
Mix.arFill(2, {Saw.ar(freq *0.5 + 2.rand2,0.6)})
]),
6000,1);
Out.ar([0,1],x * EnvGen.kr(enve, gate, doneAction: 2))
}).send(s);
)
then...
(
var keys, cutspec, cutbus, rezspec, rezbus, lfospec, lfobus;
keys = Array.newClear(128);
MIDIClient.init;
MIDIIn.connect(0, MIDIClient.sources.at(0));
g = Group.new;
cutspec = ControlSpec(100,10000,\linear,0.001);
cutbus = Bus.new(\control,1,1,s);
cutbus.value = 10000;
rezspec = ControlSpec(1,0,\linear,0.001);
rezbus = Bus.new(\control,2,1,s);
rezbus.value = 1.0;
lfospec = ControlSpec(0,50,\linear,0.001);
lfobus = Bus.new(\control,3,1,s);
MIDIIn.control = {arg src, chan, num, val;
if(num == 1,{
rezbus.value = rezspec.map(val/127.0);
});
if(num == 7,{
lfobus.value = lfospec.map(val/127.0).postln;
});
};
MIDIIn.bend = {arg src, chan, val;
cutbus.value = cutspec.map(val/16383.0);
};
MIDIIn.noteOn = {arg src, chan, num, vel;
var node;
if(num < 60, {
node = Synth.tail(g, "funk", [\freq, num.midicps, \amp, vel/255]);
node.map("cutoff",1,"rez",2,"lfospeed",3);
// node = Synth.basicNew("funk",s);
// s.sendBundle(nil,
// node.addToTailMsg(g,[\freq, num.midicps, \amp, vel/255]),
// node.mapMsg("cutoff",1,"rez",2,"lfospeed",3)
// );
keys.put(num, node)
},{
node = Synth.tail(g, "strings", [\freq, num.midicps, \amp, vel/255]);
keys.put(num, node)
});
};
MIDIIn.noteOff = {arg src, chan, num, vel;
var node;
node = keys.at(num);
if (node.notNil, {
keys.put(num, nil);
s.sendMsg("/n_set", node.nodeID, "gate", 0);
// or node.release
// then free it ... or get the NodeWatcher to do it
});
};
)