[newbie] midi control for master volume [SOLVED]

Hello guys,
I am an absolute newbie of SC.

I just wonder if there’s a way to add a code in all my patches in order to control a master volume by a MIDI controller. Just something to put by default…

MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.cc(here something ,62);

Hello!

What OS are you on? I believe that the way operating systems specify MIDI ports might be different.

I’m on MacOS, so if you are as well, things should work as described. Otherwise check out the help docs for MIDI.

The function itself should work the same, no matter you OS, except the argument for specifying which controller you want (the srcID key)

If you have a specific controller in mind, but have several, and want the function to ignore others, you can figure out its srcID by first evaluating MIDIClient.init and then MIDIIn.connectAll.

If not you can skip this next part.


See what all the sources are by providing MIDIClient the sources message to get an array of all your MIDI inputs.

MIDIClient.init;
MIDIIn.connectAll;

MIDIClient.sources;
/* mine returns the following in the post window
-> [ MIDIEndPoint("Console 1 Fader", "Console 1 Fader", -881897415), MIDIEndPoint("Sensel Morph", "Sensel Morph", 1281011945), MIDIEndPoint("Console 1", "Console 1", -920073526), MIDIEndPoint("TOUCHE_SE", "Main Port", 1922042393), MIDIEndPoint("TOUCHE_SE", "Control Port", -1670561750) ]

srcID in the MIDIdef actually is looking for the uid, which is the long string of numbers in each list).

A good way to get it that ties it to the controller rather than the order it’s initialized in is to use MIDIIn.findPort.

For instance I set this up with the Sensel Morph, so this code returns its uid:
MIDIIn.findPort("Sensel Morph", "Sensel Morph").uid

If I wanted to use the Touche, I would have written MIDIIn.findPort("TOUCHE_SE", "Main Port").uid.


The function itself exploits the fact that you can get or set the volume with s.volume or s.volume = someDecibelValue. The dB range is -90 to +6. Since MIDI CC ranges from 0 to 127, I used the linlin message to scale MIDI to dB. This allows for linear response. If you want some sort of curve, you can try .lincurve(0, 127, -90, 6, -4), where the -4 is the curve, negative values create a more exponential response, the lower the value the more exponential.

And then it’s just a matter of setting s.volume equal to that scaled value.

(
var mididb;
MIDIdef.cc(\globalVol, { |val, cc, chan, key| 
	// server volume mapped -90 though 6dB
	mididb = val.linlin(0, 127, -90, 6);
	// mididb.postln;
	// mididb = mididb.round(0.001) // optional to quantize a bit
	s.volume = mididb;
}, 
ccNum: 74, // change to desired CC value here
chan: nil, // leave nil for all channels, or specify. note! that channel count starts at 0 (MIDI Ch 1 == 0)
srcID: MIDIIn.findPort("Sensel Morph", "Sensel Morph").uid, // set nil to listen to all connected controllers, or specify a uid
);
MIDIdef.cc(\globalVol).permanent = true; // if this isn't set to true, cmd + period will kill the def
)

If you want this control to always work, and not need to be custom loaded, you can stick it in your startup file (file menu drop down > open startup file).

You’ll need to run

MIDIClient.init;
MIDIIn.connectAll;

first, so you can stick the def below those two lines.

And the best place to put is probably inside the s.waitForBoot function. If you don’t have one in your start up file, you can create one and place it in your startup file (you can only have one waitForBoot):

s.waitForBoot {
	MIDIClient.init;
	MIDIIn.connectAll;
	(
		var mididb;
		MIDIdef.cc(\globalVol, { |val, cc, chan, key| 
			// server volume mapped -90 though 6dB
			mididb = val.linlin(0, 127, -90, 6);
			// mididb.postln;
			// mididb = mididb.round(0.001) // optional to quantize a bit
			s.volume = mididb;
		}, 
		ccNum: 74, // change to desired CC value here
		chan: nil, // leave nil for all channels, or specify. note! that channel count starts at 0 (MIDI Ch 1 == 0)
		srcID: MIDIIn.findPort("Sensel Morph", "Sensel Morph").uid, // set nil to listen to all connected controllers, or specify a uid
		);
		MIDIdef.cc(\globalVol).permanent = true; // if this isn't set to true, cmd + period will kill the def
	);
};
1 Like

well! Thank you very much!
I didn’t expect something so articulated, it is really too much for my level, I would never have done it alone!

Everything works fine BUT not as a startup file (I suppose post window doesn’t print any output for the startup file…)

L-

You’re welcome. And you found the correct classes, and often that’s part of the battle!

How do you know it isn’t working? And have you restarted SC and/or rebooted the server since saving the startup file?

The startup file gets fully evaluated when you start SuperCollider or (re)boot the Interpreter. It works its way down, line by line though considering the entire file like a full block, and so every line will need semi-colons.

I kind of think of the start up file as having 3 sections.

  1. All the code at the “top” that can/needs to happen before the server boots
  2. The code inside of the function s.waitForBoot. Having this function inside of your startup file will also force the server to boot automatically. The stuff in the function happens immediately after the server boots.
  3. The code inside of the function s.doWhenBooted. This also happens right after the server boots, however it won’t automatically force the server to boot.

Things in the startup file can definitely print, typically only when there is code telling it to do so. And it will also typically print if there’s an error.

You can stick something like 1.postln; some where in there to verify the various components are being run through.

well, I think the startup file is not running the MIDI classes after the boot (I restarted SC of course).

If I try this code, commenting the MIDI classes, the midi controller is not running:

/* commented since it's in the startup file
MIDIClient.init;
MIDIIn.connectAll;
*/

(
SynthDef(\noise, {
	arg amp = 0.5, cutoff = 2000;
	var noise = WhiteNoise.ar(amp);
	var filter = BPF.ar(noise, cutoff, 0.1);
	var out = Out.ar(0,filter !2);
}).add
)

x = Synth(\noise, [\amp, 0.1]);


MIDIdef.cc(\cl1, {arg val; x.set(\cutoff, val*12)} , 53);

startup file:

s.waitForBoot {

	MIDIClient.init;

	MIDIIn.connectAll;

(

var mididb;

MIDIdef.cc(\globalVol, { |val, cc, chan, key|

			// server volume mapped -90 though 6dB

			mididb = val.linlin(0, 127, -90, 6);

			// mididb.postln;

			// mididb = mididb.round(0.001) // optional to quantize a bit

			s.volume = mididb;

		},

		ccNum: 62, // change to desired CC value here

		chan: nil, // leave nil for all channels, or specify. note! that channel count starts at 0 (MIDI Ch 1 == 0)

//		srcID: MIDIIn.findPort("Sensel Morph", "Sensel Morph").uid, // set nil to listen to all connected controllers, or specify a uid
		srcID: MIDIIn.findPort("MIDI Mix", "MIDI Mix MIDI 1").uid, // set nil to listen to all connected controllers, or specify a uid

		);

		MIDIdef.cc(\globalVol).permanent = true; // if this isn't set to true, cmd + period will kill the def

	);

};

What happens if you select all the code that’s in your startup file and evaluate it after the server has booted?

ERROR: syntax error, unexpected KEYBINOP, expecting $end
  in interpreted text
  line 1 char 7:

  		chan: nil, // leave nil for all channels, or specify. note! that channel count starts at 0 (MIDI Ch 1 == 0) 
    ^^^^^
-----------------------------------
ERROR: Command line parse failed
-> nil

I just tried it here too and got a different error, but I think I know what’s causing it: the variable declaration.

Try this:

s.waitForBoot {
	
	MIDIClient.init;
	
	MIDIIn.connectAll;
	
	(
		{
			var mididb;
			
			MIDIdef.cc(\globalVol, { |val, cc, chan, key|
				
				// server volume mapped -90 though 6dB
				
				mididb = val.linlin(0, 127, -90, 6);
				
				// mididb.postln;
				
				// mididb = mididb.round(0.001) // optional to quantize a bit
				
				s.volume = mididb;
				
			},
			
			ccNum: 62, // change to desired CC value here
			
			chan: nil, // leave nil for all channels, or specify. note! that channel count starts at 0 (MIDI Ch 1 == 0)
			
			// srcID: MIDIIn.findPort("Sensel Morph", "Sensel Morph").uid, // set nil to listen to all connected controllers, or specify a uid
			srcID: MIDIIn.findPort("MIDI Mix", "MIDI Mix MIDI 1").uid, // set nil to listen to all connected controllers, or specify a uid
			
			);
			
			MIDIdef.cc(\globalVol).permanent = true; // if this isn't set to true, cmd + period will kill the def
		}.value
	);
	
};
1 Like

Stunning! It works!
Thanks a lot for your help!!

Definetely with lincurve is more precise!

				mididb = val.lincurve(0, 127, -90, 6, -4);

1 Like