Class for the 16n Faderbank

I have this help class that takes care of setting up control busses for a 16n Faderbank.

Thought I would share, and also invite some criticism.
One plan I have is to allow for custom midi message setting, since it is now defaulted to the midi numbers the device default setting sends to.
Another is to handle several instances, through maybe an Dictionary, in case 16 faders are not enough. Perhaps by using the uid number as a key.

/* 
*			16n Supercollider Class 
*/

SixteenFaders {
	classvar func = nil;
	var <fader;
    var enable = true;
    var physical = true;
    var id;

	*new {
		^super.new.init();
	}

	init {
		var found = "16n found";
        var server = Server.default;
		var sources;
		MIDIClient.init();
		MIDIIn.connectAll;
		sources = MIDIClient.sources;

		// Check if 16n is in MIDIEndPoints, store uid in global var.
		(
        sources.do{|source|
            if (source.device == "16n") {
                id = source.uid;
                found.postln;
                physical = true;
                };
            };
		);

		// MIDIdef with correct midi \uid (srcID)
		(
			fader = 16.collect{
				Bus.control(server, 1);
			};

			if (func.isNil == false) {
                // free existing midifunc
                func.free;
            };

            if (physical) {
                func = MIDIFunc.new({
                    |val, num, chan, src|
                    if (enable) {
                        ("***	Fader: " ++ '[ ' ++ (num - 32) ++ ' ]' ++ 
                        "	Value: " ++ '[ ' ++ val ++ ' ]').postln;
                    };

                    switch(num, 
                        32, { fader[0].set(val.linlin(0,127,0,1)) },
                        33, { fader[1].set(val.linlin(0,127,0,1)) },
                        34, { fader[2].set(val.linlin(0,127,0,1)) },
                        35, { fader[3].set(val.linlin(0,127,0,1)) },
                        36, { fader[4].set(val.linlin(0,127,0,1)) },
                        37, { fader[5].set(val.linlin(0,127,0,1)) },
                        38, { fader[6].set(val.linlin(0,127,0,1)) },
                        39, { fader[7].set(val.linlin(0,127,0,1)) },
                        40, { fader[8].set(val.linlin(0,127,0,1)) },
                        41, { fader[9].set(val.linlin(0,127,0,1)) },
                        42, { fader[10].set(val.linlin(0,127,0,1)) },
                        43, { fader[11].set(val.linlin(0,127,0,1)) },
                        44, { fader[12].set(val.linlin(0,127,0,1)) },
                        45, { fader[13].set(val.linlin(0,127,0,1)) },
                        46, { fader[14].set(val.linlin(0,127,0,1)) },
                        47, { fader[15].set(val.linlin(0,127,0,1)) },

                    )
                }, msgNum: Array.series(16,32,1), chan: 0, msgType: \control, srcID: id);
            } {
                postln("No 16n was found, no MIDIFunc created");
                // TODO: Create a mock QT faderbank for testing
            }
		);
	}

    faderAt {|faderPosition|
        ^fader[faderPosition];

    }

	enablePost {
		enable = true;
	}

	disablePost {
		enable = false;
	}

	usage { 
      var usage = "Usage: A 'Bus' object is accessed by <instance of object>.fader[n] or \n<instance of object>.faderAt(n). 'n' is the corresponding fader number, \nbut zero-indexed. ('0' gets you the first fader)\n";
        Post << "|----------------------------------------------------------------------//\n" << usage << "|----------------------------------------------------------------------//\n";
	}
}

Great for sharing. I have a 16n and I want to use it in supercollider, but I am a novice in this field
How can I use it, do you have an example for sharing, would be great?

Sure!

There are some assumptions of how the 16n is configured, such as which midi number correspond to which fader. I believe it’s the default starting at 32 and up.

It convert the input from integer values from 0…127 into float values from 0.0…1.0, bc I think it’s good practice.

f = SixteenFaders.new;

	// VOICE 1
        Pbind.new(
		\instrument, \someSynth,
		\freq, f.faderAt(0).asMap,
		\vol, f.faderAt(1).asMap,
	).play(quant: 1);

// or

Synth(\playback, [\rate, 0.35, \t_trig, 1, \vol, f.faderAt(15).asMap])

The .asMap method is called on the control bus inside the SixteenFaders class, and should give you realtime control, but for controlling variables inside a loop or routine, you could use the .getSynchronous to sample the value at that time.

I should expand the readme, but here is a project where I use it a lot:
github.com/vsandstrom/EP

Thanks, get the 16n “initialized”, but your example above misses the Synth(\playback…) sorry dump question.
BTW, what is the difference between the files .sc and .scd? Are .sc class-files and .scd running code?

I’ll update the readme on GitHub to have a proper example.

The .sc suffix is for supercollider class files, in relation to .scd which are the regular project “document” files.

.sc files should be placed in the Extensions folder. You can find it by evaluating

Platform.userExtensionDir

from inside supercollider

A couple of thoughts.

Do you want to initialize MIDI even if MIDI was already initialized? Perhaps if(MIDIClient.initialized.not) { ... do the init here ... }.

So control number 32 maps to index 0, and 33 to index 1, and 34 to index 2… and all the actions are the same.

What about dropping the switch in favor of if(num.inclusivelyBetween(32, 47)) { fader[num-32].set(...) }? One line instead of 16.

If you want flexible mapping, you could make a Dictionary where the keys are cc numbers, and the values are slider indices…

Then… “linlin” = ((value - inLo) / (inHi - inLo)) * (outHi - outLo) + outLo = ((value - 0) / (127 - 0)) * (1 - 0) + 0, look how many of the operations disappear: every -0 and +0 is a no-op, so (value / 127) * 1 and * 1 is also a no-op so it’s really just value/127.

hjh

Thanks for the review! I didn’t know sc had a built in range, but I should of course use it! And I should definitely use a dict instead, and linlin seems a bit redundant.

I’ve updated the readme with a working example. Hope it makes sense!

Thanks, appreciating your effort!

By making this a classvar you are making it impossible to have more than one of these devices plugged it at once. Likewise the bit where you loop through the midi sources has issues when more than one of these devices is plugged in, will it only let you use the last one, or will the first no longer respond?

I’d recommend you pull out the attaching process into another class and remove all global variables to avoid this issue.

I mean, it’s not that likely the user has two of these… But you never know!

I actually have two of these and was wondering what happens if I try to use them in combination!

At this point you cannot, since each instance of this class will overwrite the previous midifunc listening to the MIDI device. I actually also have two of these, but have never had a reason to use them both at the same time. Although ofc you should be able to if that is where you heart lies.

I am thinking of rewriting this, instead handling collisions in midi numbering, where you have to supply an argument with lowest midi number on .init().
Not really sure how, but I’ll get around to it soon enough.