MIDIKtl howTo write subclasses for your own controller devices
This is a documented version of the PFKtl class.
For implementing shiftable mapped areas on NodeProxyEditors and ProxyMixers,
see the NanoKtl class.
// --- Class file MyOwnKtl.sc ---//
MyOwnKtl : MIDIKtl { // put your Ktl name here
classvar <>verbose = false; // debugging flag
var <>softWithin = 0.05; // will use softSet, so have a tweakable limit for it
var <lastVals; // remember the last values for better control
var <valRange, <minval, <range; // some MIDI controllers may not reach full 0-127 range,
// provide a global range for that.
// (if the range is different for each controller,
// make a dict of all ranges.)
init {
super.init; // get the ctlNames created for MyOwnKtl
ctlNames = defaults[this.class];
orderedCtlNames = ctlNames.keys.asArray.sort; // a sorted list of the names
lastVals = (); // initialise lastVals
this.valRange = [0, 127]; // set default valRange;
}
// makeDefaults puts ctlNames for this device/class
// into MIDIKtl.defaults.
*makeDefaults {
// just one bank of sliders
defaults.put(this,
( // clear names for the elements;
// '0_7' is midi chan 0, cc number 7,
// combined into a symbol for speed.
sl1: '0_7', sl2: '1_7', sl3: '2_7', sl4: '3_7',
sl5: '4_7', sl6: '5_7', sl7: '6_7', sl8: '7_7',
sl9: '8_7', sl10: '9_7', sl11: '10_7', sl12: '11_7',
sl13: '12_7', sl14: '13_7', sl15: '14_7', sl16: '15_7'
)
);
}
// a method for registering actions based on ctl name.
mapCC { |ctl= \sl1, action|
var ccDictKey = ctlNames[ctl]; // e.g. '0_42'
if (ccDictKey.isNil) {
warn("key % : no chan_ccnum found!\n".format(ctl));
} {
ccDict.put(ccDictKey, action);
}
}
// methods to map to various JITGuis:
// EnvirGui, TdefGui, PdefGui, NdefGui, and ProxyMixer.
// these have a number of design decisions based on
// how this device can work best for these kinds of guis.
// assume this has only a limited number of items (sliders)
mapToEnvirGui { |gui, indices|
var elementKeys;
// which slider keys to be used for the gui?
indices = indices ? (1..8);
elementKeys = orderedCtlNames[indices - 1];
// for each slider,
// get the editKey, the sliders lastVal,
// and do use softSet for setting the proxy's param.
//
elementKeys.do { |key, i|
this.mapCC(key,
{ |ccval|
var envir = gui.envir;
var parKey = gui.editKeys[i];
var normVal = this.norm(ccval);
var lastVal = lastVals[key];
if (envir.notNil and: { parKey.notNil } ) {
envir.softSet(parKey, normVal, softWithin, false, lastVal, gui.getSpec(parKey))
};
// remember lastVal for the next time
lastVals.put(key, normVal) ;
}
)
};
}
// for PdefGui and TdefGui, just map to their EnvirGui.
mapToPdefGui { |gui, indices|
this.mapToEnvirGui(gui.envirGui, indices);
}
mapToTdefGui { |gui, indices|
this.mapToEnvirGui(gui.envirGui, indices);
}
// NdefGui:
mapToNdefGui { |gui, indices, lastIsVol = true|
var elementKeys, lastKey;
indices = indices ? (1..8);
elementKeys = orderedCtlNames[indices - 1].postcs;
/// last slider can optionally be a volume control
if (lastIsVol) {
lastKey = elementKeys.pop;
indices.pop;
// use last slider for proxy volume
this.mapCC(lastKey, { |ccval|
var lastVal = lastVals[lastKey];
var mappedVol = \amp.asSpec.map(this.norm(ccval));
var proxy = gui.proxy;
if (proxy.notNil) { proxy.softVol_(mappedVol, softWithin, lastVal: lastVal) };
lastVals[lastKey] = mappedVol;
});
};
// the other sliders go to the paramGui and will set those
this.mapToEnvirGui(gui.paramGui, indices);
}
// ProxyMixer is more complex:
// here it is again assumed that the mixer has no more than the number of
// available sliders. if you scoll on the mixer,
// they are still reachable.
mapToMixer { |mixer, numVols = 8, indices, lastEdIsVol = true, lastIsMaster = true|
var server = mixer.proxyspace.server;
var elementKeys, lastKey, spec;
indices = indices ? (1..16);
elementKeys = orderedCtlNames[indices - 1];
// add master volume on slider 16
if (lastIsMaster) {
lastKey = elementKeys.pop;
spec = Spec.add(\mastaVol, [server.volume.min, server.volume.max, \db]);
// this.mapCC(lastKey, Volume.softMasterVol(0.05, server, \midi.asSpec));
this.mapCC(lastKey, { |ccval| server.volume.volume_(spec.map(this.norm(ccval))) });
};
// map first n sliders to proxy volumes
elementKeys.keep(numVols).do { |key, i|
this.mapCC(key,
{ |ccval|
var proxy = mixer.arGuis[i].proxy;
var lastVal, mappedVal, lastVol;
var spec = \amp.asSpec;
if (proxy.notNil) {
lastVal = lastVals[key];
mappedVal = spec.map(this.norm(ccval));
lastVol = if (lastVal.notNil) { spec.asSpec.map(lastVal) };
proxy.softVol_(spec.map(mappedVal), softWithin, true, lastVol );
};
// [key, proxy.key, mappedVal].postcs;
lastVals[key] = mappedVal;
};
)
};
// map the rest of the sliders to the NdefGui!
this.mapToNdefGui(mixer.editGui, (numVols + 1 .. elementKeys.size), lastEdIsVol);
}
}
// --- end of Class file MyOwnKtl.sc ---//