Map value range of midi control bus

Hey there! I’d like to map the value range of an incoming midi slider AFTER I assigned it to a control bus. The goal would be just to initialize all midi sliders and knobs I have and assign it afterwards in a flexible way to different synths and pdefs. This is what I had in mind - does something like this even exist or is it mandatory to do it in the MIDIdef? The advantage would be to also be able to assign the same midi control to different synths with different value mappings…

~slider0 = Bus.control(s, 1);
~slider1 = Bus.control(s, 1);

MIDIdef.cc(\controller, {
	arg val, num;

	case
	{ num == 0 } { ~slider0.set(val); }
	{ num == 1 } { ~slider1.set(val); }
	{ true } { nil };
});

SynthDef.new(\sine, {
	arg freq = 440, amp;
	var sig;
	
	sig = SinOsc.ar(freq!2);
	sig = sig * amp;

	Out.ar(out, sig);
}).add;

~synth = Synth(\sine, [
	freq: ~slider0.asMap.linlin(0,127, 200, 400);
	amp: ~slider1.asMap.linlin(0,127, 0.0, 1.0);
]);

Thank you already for ideas and suggestions on this.

Best,
chk

Move the mapping into the synth, but do it programatically - i.e., stop using the outdated args and use NamedControls -, and normalise the midi input range.

~sliders = 2.collect{ Bus.control(s, 1) };

// normalise midi to 0 - 1
MIDIdef.cc(\controller, { | val, num | ~sliders[num].set(val / 127) }); 

SynthDef.new(\sine, {
	var mkMappedBus = {|sym, defaultRange=([0, 1]), type=\linlin| 
		var v = NamedControl.kr(sym);
		var range = NamedControl.kr(sym ++ 'Range', defaultRange);
		v.perform(type, 0, 1, *range)
	};

// ref. A
	var freq = mkMappedBus.(\freq, [20, 20000], \linexp);
	var amp = mkMappedBus.(\amp, [0, 1], \linlin);
	
	var sig = SinOsc.ar(freq);
	
	Out.ar(sig, sig!2 * amp);
}).add;

~synth = Synth(\sine, [
	\freq: ~sliders[0].asMap,
	\freqRange: [220, 440],
]);

At ref A, mkMappedBus will return two NamedControls one called freq, the other freqRange.

1 Like

Thank you for your answer! Will definitely look into NamedControls and normalising the MIDI range is generelly a great idea.

But isn’t there a way to send a certain range of a control bus very flexible into a SynthDef, therefore to do the mapping of the MIDI range without touching the SynthDef itself at all anymore? In this case I would have to change the SynthDef again everytime I decided to apply the midi control to another part of it…

I was thinking of having a method here which I could apply to every SynthDef I have every written, and to be able to map the midi values to any arg I have every defined. Does that make sense?

I don’t think so. You might get something more flexible using JITLib, but I’m not familiar with it as I only work with fixed structures.

Well it only involes replacing all the argument names. Assuming you have used args throughout, this is a pretty easy copy and paste…its probably possible in regex (but your on your own there).

No, just change all the parameters in all the synthDefs to use mkMappedBus, the var will default to the lowest value in the range, but you could easily change that with another argument to mkMappedBus.
Then you can do synth.set(\freq, 0) and/or synth.set(\freqRange, [20, 60]) as needed.

To do exactly what you want would require making a function to create (for each mapping) a new bus along with a mapping synth… perfectly do-able, but you will then have to do everything in forked functions. The solution I’ve posted is better, although it requires editing the old synthdefs… but if you’ve used the old args style, then you probably ought to do a refactor anyway…

Like this if you really want to remap on the fly…

(
~map_bus = { |parentSynth, normedInputBus, spec|
	var sync = { |f| var r = f.(); s.sync; r };
	var per = normedInputBus.rate;
	var arOrKr = if(per == \audio, \ar, \kr);
	
	var outBus = sync.({ Bus.perform(per, s, normedInputBus.numChannels) });
	var def = sync.({
		SynthDef(\tempMapp, {
			var in = In.perform(arOrKr, normedInputBus, normedInputBus.numChannels);
			var out = spec.map(in);
			Out.perform(arOrKr, outBus, out);
		}).add;
	});
	var mapper = sync.({Synth.before(parentSynth, \tempMapp) });
	parentSynth.onFree({
		"free".postln;
		mapper.free;
		outBus.free;
	});
	outBus;
};
)

~synth = { \val.kr().poll }.play;

~bus = Bus.control(s, 1)

(
s.waitForBoot {
	~synth.map(\val, ~map_bus.(~synth, ~bus, \freq.asSpec))
}
)

~bus.set(0.0)
~bus.set(1.0)

~synth.free

I’m using a ControlSpec to do the mapping here, makes the most sense I think.

Saying args style is outdated is a little bit rough. Named Control is great, but Args style is still good in certain scenarios. Unless maybe you’re stating factually that Args is getting deprecated? I hope not.

2 Likes

The huge problem with arg style in my opinion, is that its a massive lie, and therefore gets in the way of learning supercollider - its a pedagogical issue, rather than a technical one.

It gives the impression that the function is actually a function and will be called. Its not, its just an Object that is used to build the specification that will be sent to the server. When you use a Ugen in a synth def, you never actually call it in an audio processing context. When the synthdef function is evaluated it appends some metadata to a global variable. Similarly, NamedControl will append its metadata. When you use args, this nasty function SynthDef.addControlsFromArgsOfFunc is called converting the args into controls.

In the context of this thread, thinking it terms of NamedControls makes programatically adding new controls based off of symbols a very simple issue, whilst it is impossible if you think it terms of args. That is why its a pedagogical issue, rather than a technical, and should be considered outdated - i.e., do not teach, not, do not use - as there are multiple benefits and little added complexity.

… one caveat, is that I always use args when using SynthDef.wrap if i want to inject some signal. Here, it actually is a function and makes sense to use it like one.

Yes exactly, in the context of this thread - Named Control works well.

Just like the how the context of working on well defined problems, with clear objectives as a member of a team is very different to the context of doing fast, messy, exploratory creative work on your own.

I’m not a teacher, so I can only comment anecdotally. But it seems to me that something else that makes learning easier is low friction. And ‘best practices’ can counter intuitively, add friction and make something more intimidating than it needs to be. People get worried if they are doing something the ‘right way’ instead of just focusing on solving the problem.

As we gain confidence we can decide for ourselves if the cost of a best practice is going to be worth the gains in out unique context.

And sure there are times when a fundamental misunderstanding is hampering someones ability to solve a problem. But it’s totally possible to provide insight into a fundamental mechanism/conceptual idea without the blanket statements that always, inevitably come with caveats.

I could not disagree with this more.
I think if something is complex, it should be slowly and carefully taught as such. Not given a quick hack thats familiar and nice, but will only see you so far then you will have to relearn it, and change the way you think, causing all manner of issues.

Arg style work in some cases, but when it doesn’t it is a huge conceptual leap to understand what is actually happening (for example, if you are trying to have a specific rate argument).
Whereas, if you learn NamedControl from the beginning, it is no more difficult that In or Out, in fact, it encourages you think more in terms of UGens which is always better.

It is 2 key presses slower: \freq.kr vs |freq| and those two keys (“kr”) contain a lot of important information. If you want a default value, particularly if its an array, (i.e. \freq.kr([20, 421, 424])) named control is always easier to type and clearer. I also disagree that creative work has to be messy, once you setup and define some constraints or basic structures (assuming they are well written) it is very quick and clean to manipulate them. Further, to develop a sustainable practice, one where your work is legible and reusable, it is important to take a moment to refactor.

The way you learn the language defines your intuition and understand of said language. This is essentially the SapirWhorf hypothesis applied to programming languages, there are lots of articles and book on this in computer science literature. So I think this is quite a bad argument, never mind that NamedControl is very intuitive and the arg style hides lots of important information. I think its like saying, I don’t know named control, therefore it is unfamiliar to me, therefore I don’t want to learn it.

Ultimately, I think this sums up what I’m trying to say. NamedControl works well, not just in this use case, but in all, since it is very little effort to write, encourages one to think in terms of UGens mores, allows for programmatically create controls as simply as concatenating strings, and therefore, it should be the default - or at least the way supercollider is taught and spoken about in the literature, and this forum counts as that.

… yet again a thread has turned into named control vs args… but that just goes to show how simple this is to solve with named control … ('freq' ++ 'Range').kr([0,1]) and how difficult it is with args (you’d need some type of reflection whilst inside the function…).
Anway, I think the two solutions I posted solve the issue. One is moving the mapping inside the synthdef, the other makes a new one. Perhaps someone has a better idea?

Thank you very much for the replies, it works great with the offered solution using NamedControl.

However, I am still interested in a solution here that would work with arguments and a mapping of the value from outside, which would be a very flexible approach. Now I agree with you that it might not be the cleanest solution or something that will introduce me to deeper concepts in Supercollider. But in some cases I also agree with the approach of “fast, messy, explorative work” - being able to just pipe any value from any source in every value range would be really great when trying to actually develop something meaningful and creative. I can totally value both points of view on this interesting discussion, and luckily I am not somebody who is frightened of being pointed to new and unknown concepts.

By the way, the next thing I wanted to send control values in from midi controllers were Pdefs, and there is a solution that is working exactly like I imagined it to be working for SynthDefs.

I made a utility class to map midi control to Synth args

and the helpfile is

Hope it helps !

1 Like

For that, the second solution I posted does the job…

The pattern solution is perhaps easier, you just get the value in a pfunc.

1 Like

My point was not actually about named control vs args. My point is that both are useful and that it’s misleading to say a feature of the language is outdated because you believe it to be inferior in all cases, in your experience.

This is what I mean when I say ‘best practices’ can add friction. So someone comes from the getting started tutorial and now args is supposedly outdated?

This is not sapir whorf or cognitive dissonance or being entrenched or whatever.
Both are useful, and I agree named control does a good job at providing the right intuition.

I’ve found named control is often not just two extra chars. Some synth defs can get ugly when named controls have to be used in more than one place - due to them having to be assigned to a variable. Some controls are assigned to vars at the top, while others appear in line. Which makes them harder to visually parse. Of course I could just assign all controls to variables at the top. That’s a ton of extra typing. The synths can get very long and it’s still not as legible as a simple args list.

I’ve gone into the weeds a bit but this is just what I’ve encountered in my personal workflow. That there are times where I prefer args and others when I l like named control.
It doesn’t have to be one or the other, and it certainly doesn’t have to be ‘args is outdated’.

I came out swinging too hard yesterday and have to apologise. I’d probably delete (or at least edit down) some of my previous comments, but then the thread wouldn’t make sense…

I’m certainly not the code police: each individual project has its own needs and some times aspects of best practice have to be broken - I’m certainly not casting judgement on that.

Yet… I do think that if you learn NamedControl, and some other more verbose ways of working, first, they will form your understanding of the language in a more complete way than if you start with things like args. After this, if one decided to use args or another method that is their decision - here it really is a value judgement with no absolute correct answer despite the impression some of my previous comments gave. I’m also not suggesting that one should feel compelled to rewrite old code just because it uses args.

No need to apologise. I personally find forums a pretty unnatural format for these discussions in a lot of ways. Text or our ‘walls of text’ can make things seem way more intense and adversarial than if we were to just chat in person.

I can totally see the value in this approach.

My main problem with \freq.kr or NamedControl is that arguments can be spread all over the code (even hiding on column 500 of a long line - which would be bad practice obviously, but is not forbidden) and therefore they require me to scan the complete synthdef body code to find out what are possible arguments. At least the args approach got that part right. It is the only reason I highly prefer args to alternatives, where possible.

Hey! So coming back to this I can see now why NamedControl is a great approach in this case and generally using them instead of args.

Is it also possible to move the function that is generating the NamedControls and the mapping out of the SynthDef? Somehow to have a little helper that I can just call from within a SynthDef? Or does it have to be written in every SynthDef making use of it?

@shiihs, as far as I can see you can also put the argument right on top of the SynthDef into a variable, like so:

var freq = \freq.kr(440);

Like that you can collect the arguments at the top. I really like though to directly be able to see inside a SynthDef in which line the argument is going in.

Nope, you can save the function in a global or even write a class that does it. It will be evaluated when the synthdef is made, but won’t contribute to the server side code directly.

If you only use symbols for the named input (which you should) they will be highlighted in green by scide, making it easier to see where they are in the code, which can eliminate the need for assigning them to a variable. Not a perfect fix, but if the synthdef is reasonably small it works well enough.

1 Like