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 ---//