VoicerMIDISocket 


Allows voicers to be assigned to MIDI channels. Several voicers can be assigned to the same channel, with different key ranges allowing for key splits. MIDI controllers can be attached to arguments in the synthdef as well. 


Uses MIDIPort and MIDIChannel, which are the backbone of a MIDI responder framework that will be more flexible than that in the crucial library. More types of sockets will have to be written to duplicate and expand the crucial MIDI responder functionality, but this is a good beginning. 


James Harkins

jamshark70@dewdrop-world.net


Creation 


*new(channel, destination, lowkey = 0, hikey = 127)


chan: the MIDI channel number to use. May be specified as:


MIDIChannelIndex(port#, channel#)  // if channel# is omitted, it will be assumed 0

[port#, channel#]

channel# // assumes port 0

nil // assumes port 0, channel 0


destination: the voicer to play. This may also be a VoicerProxy for on-the-fly MIDI re-routing.

lowkey, hikey: the range of MIDI note numbers to which this voicer will respond. These default to the entire range. 


free 


Removes this MIDI socket from its MIDI channel. If you have multiple MIDI sockets for a given channel, the others will be left intact. To clear a MIDI channel completely, do one of the following: 


MIDIPort.removeAt(channel_number); 

//or -- you don't need to do both!

myVoicerSocket.parent.free; 


Controls 


transpose_(semitones) 


This number of semitones will be added to the MIDI note number when the note is triggered. 


addControl(ccnum, name, value = 0, spec) 


Maps a MIDI controller number to a single argument.


ccnum: the controller number. Supply \pb to use the pitch bend wheel. Alternately, leave ccnum nil and the socket will get a control from the MIDIChannel's CCAllocator (accessed by channel.ccAllocator).

name: the name of the synth argument. Should be a symbol (or respond to asSymbol). 

value: the desired initial value for this controller. 

spec: a ControlSpec used to map the raw controller data onto a user defined range. The controller data are divided by 127 before being mapped by the ControlSpec, so the input range is an 0..1. Can be given as an Array (to which .asSpec will be applied). For pitch bend, if you supply an Integer or Float, addControl will construct a spec which will give that many semitones bend in either direction: [num.midiratio.reciprocal, num.midiratio, \exponential, 0, 1].asSpec.


The same controller number can be mapped to many different functions (and even belonging to different voicers or different kinds of objects altogether). If you want to control multiple voicers with one controller, keep in mind that each voicer has to have its own socket; therefore, you'll have to add a control to each socket individually, using the same controller number.


removeControl(control) 


Disconnects a global control from its MIDI controller. You can specify either the controller number or the argument name. The control will still be global in the voicer for programmatic use.


Example


This example illustrates connecting a Voicer, mapping global controls to MIDI controllers, GUI interactivity and pitch bend.


(

i = Instr([\test, \miditest], {

arg freq = 440, gate = 0, env, pb = 1, ffreq = 1000, rq = 1;

var out, amp;

amp = Latch.kr(gate, gate); // velocity sensitivity

out = EnvGen.kr(env, gate, doneAction:2) *

RLPF.ar(Pulse.ar(freq * pb, 0.25, amp), ffreq, rq);

[out,out]

}, [\freq, \amp, nil, nil, \freq, \rq]);


v = Voicer(10, i, [\env, Env.adsr(0.01, 0.2, 0.75, 0.1), \rq, `0.2]);


k = VoicerMIDISocket(0, v);

k.addControl(1, \ffreq, 1000, \freq);   // filt. cutoff by mw

k.addControl(\pb, \pb, 1, 3); // 3-semitone bend


v.gui; // the controllers show up in the window, w/ visual feedback when you move the wheels

)


// when done

v.free;


Assigning arbitrary arguments


You can set up streams for any synth arguments. The streams are generated using any event pattern (such as Pbind). The event stream will be evaluated for every note-on, and the results added to the arguments array when triggering the voicer.


noteOnArgsPat_(pattern)


Example:


(

i = Instr([\test, \miditest], {

arg freq = 440, gate = 0, env, pb = 1, ffreq = 1000, rq = 1;

var out, amp;

amp = Latch.kr(gate, gate); // velocity sensitivity

out = EnvGen.kr(env, gate, doneAction:2) *

RLPF.ar(Pulse.ar(freq * pb, 0.25, amp), ffreq, rq);

[out,out]

}, [\freq, \amp, nil, nil, \freq, \rq]);


v = Voicer(10, i, [\env, Env.adsr(0.01, 0.2, 0.75, 0.1), \rq, `0.2]);


k = VoicerMIDISocket(0, v);

k.addControl(\pb, \pb, 1, 3); // 3-semitone bend

k.noteOnArgsPat = Pbind(\ffreq, Pn(Pgeom(300, 1.07177346, 20), inf));

k.noteOnArgsEvent = (gate:0.25); // gate is 0.25 for every note, overriding velocity sensitivity

)


// when done

v.free;


Tuning schemes


The default tuning scheme is equal temperament (midicps). You can write a function to translate MIDI note numbers into frequencies using any method you like. The example below creates a lookup table for some sort of just intonation.


Also see the classes in the ddwTemperament quark. The tuning in the example below could be written more simply using TuningRatios, provided ddwTemperament is installed.


k.midiToFreq = TuningRatios(12, tunings: [1, 135/128, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 14/9, 27/16, 16/9, 15/8]);


Note: If you change the midiToFreq function while holding notes on the keyboard, you are very likely to get stuck notes because the release frequency for those notes will be different from the onset frequency.


midiToFreq_(func)

VoicerMIDISocket.defaultMidiToFreq_(func)  // this will be applied to all new sockets


(

var freqs, base;

base = 0.midicps;

freqs = [1, 135/128, 9/8, 6/5, 5/4, 4/3, 45/32, 3/2, 14/9, 27/16, 16/9, 15/8] * base;

// the next line expands the one octave upward for a continuous scale

// see the Adverbs helpfile for an explanation of *.x

freqs = 2 ** (0..10) *.x freqs;


i = Instr([\test, \miditest], {

arg freq = 440, gate = 0, env, pb = 1, ffreq = 1000, rq = 1;

var out, amp;

amp = Latch.kr(gate, gate); // velocity sensitivity

out = EnvGen.kr(env, gate, doneAction:2) *

RLPF.ar(Pulse.ar(freq * pb, 0.25, amp), ffreq, rq);

[out,out]

}, [\freq, \amp, nil, nil, \freq, \rq]);


v = Voicer(10, i, [\env, Env.adsr(0.01, 0.2, 0.75, 0.1), \rq, `0.2]);


k = VoicerMIDISocket(0, v);

k.addControl(1, \ffreq, 1000, \freq);   // filt. cutoff by mw

k.addControl(\pb, \pb, 1, 3); // 3-semitone bend

k.midiToFreq_(freqs[_]);  // same as: v.midiToFreq_({ |note| freqs[note] });

)


k.midiToFreq_(nil); // go back to default

// C major triads are ugly again!

v.free;