How to make incremental/decremental midi rotary encoders?

I want to do this:

MIDIdef.cc({
|val |; 
< code here >
~bus.set(newval);
},100);

Where < code here> functions like HPZ1 and sends newval. HPZ1 is used to find the difference between a new value and a previous value, 0.5*( n(t ) - n( t - 1 ) ). HPZ1 works at ar,kr rate, but MIDIDef is triggered only when a midi message is sent to it. What this means is HPZ1, because it is constantly polling, can output 0, i.e. no change, but in MIDIDef, a difference equation is never zero because there are no midi events that arenā€™t different from the previous event, ie <cc 100, value 27 > , < cc 100, value 35 >, but never <cc100,val10>, <cc100,val10> sequentially. Is there a way of getting MIDI code to work like that? I donā€™t really want to set up a metro like system that constantly polls or resends the MIDI events to get the difference output.

Also does MIDIDef reside on the client? Because Out.kr doesnā€™t seem to work in it.

Ok just so I can search this again in the future. The problem is MIDIdefs are triggered by midi events, In.ar/kr are polling constantly, so if a MIDIdef sets a bus to a number, since mididefs cannot use the Out.kr/ar paradigm, the polling done in a Synth is so fast, a difference in value at time t and t -1 is zero.

To help with this, a Lag is introduced so the Synth can use the MIDIdef set bus values.

// in , out and state are control busses

SynthDef(\diffnumbers,{
 arg in,out,state;
 var a,b,c;
 a=In,kr(in);
 b=Lag.kr(In.kr(state),1);
 c= a - b;
 Out.kr(out,c);
 Out.kr(state,a);
}).add;

MIDIdef and OSCdef are functions that reside on the client.

You donā€™t need the server at all for this.

(
var previous = 0;  // or 64? whatever is neutral for your device

MIDIdef.cc(\rotary, { |val|
	var diff = val - previous;
	previous = val;  // save for next time
	case
	{ diff < 0 } {
		... roll back...
	}
	{ diff > 0 } {
		... roll forward...
	}
}, ccNum: 100);
)

Involving control buses etc. is way too much complication, more chances for bugs. (Also, in the server, a two-point differentiator is HPZ1 ā€“ but Iā€™d strongly suggest to keep it in the language.)

hjh

Yes that works as well. A better idea too, since thereā€™s no busses that double up as memory.

Ok so the mididef as a way of caching previous information to get the difference doesnā€™t really work. It seems that every event is caught, so the rate of change of the encoder moving isnā€™t reflected, ie. even if the encoder was turned 100 from the previous value, each 1 step up is counted, so the difference is always 1 or -1 or 0, which is probably why thereā€™s a clock attached to an operation that needs to know by how fast things are changing, like HPZ1, which runs at ar/kr to test the change of a variable.

The language side does have a clock you can poll: SystemClock.seconds tells you the current logical time. You could have a prevTime variable and then know how much time elapsed since the last incoming message.

Iā€™m not convinced that the bus approach will inherently fix this problem for you. Youā€™ll still get one impulse per incoming message. Timed triggers might help; not sure.

Btw if the desire is to filter out messages that are coming in too fast, one solution is the ddwSpeedLim quark, which implements something like [speedlim] from Max. You can set a minimum time between messages to pass through; too high a rate will drop messages in the middle. (Iā€™m not certain this will completely meet your needs; it will slow down the rate of messages.)

hjh

PS The deleted message is because my phone sent the post before I was finishedā€¦

Below is an example that gives you the time based derivative of the midi value.
You can add a small lag factor, called smooth below, to perhaps get something similar to what you want?

~make_midi_rotary_bus = {|ccnum, chan, group, smooth=0|
	var value = Bus.control(s, 1);
	var delta = Bus.control(s, 1);
	
	var synth = {
		Out.kr(delta, Slope.kr(In.kr(value, 1).lag2(smooth)));
		// or ....  Out.kr(delta, Slope.kr(In.kr(value, 1)).lag2(smooth));
	}.play(target: group);
	
	var func = MIDIFunc.cc(
		{ |v| value.set(v) }, ccnum, chan
	);
	
	(
		\value_bus: value, \delta_bus: delta, 
		\midi_func: func, \synth_instance: synth, 
		\free_me : {|self| self.do{|i| try {i.free}{} }; "free'd" }
	)
};

~my_rotatary = ~make_midi_rotary_bus.(ccnum: 1, chan: 1, smooth: 0.2);

~my_rotatary.delta_bus.scope; // here is the delta
~my_rotatary.value_bus.scope; // here is the value

MIDIIn.connectAll
MIDIIn.doControlAction(1, 1, 1, 2); // spoof a cc

~my_rotatary.free_me(); // use this to delete the synth and free the busses

This is because the values are digital and quantised, so there is no change most of the time, and then, very suddenly, an instantaneous jump (an infinite first derivative).

The question is how often do you want to measure the derivative? In the above code Iā€™ve side stepped this by smoothing the value, its a bit of a hack, but works.
You could however, using the language, sample the value every second and compute its derivative, this is simple with a Routine and loopā€¦ but you could also measure the derivative at the end of each ā€˜gestureā€™, meaning when ever the knob changes direction, or experiences a sudden change in velocity.

This is a great example of coding style. The supercollider help files should use this. The examples in the help browser, donā€™t really delve into how to structure code, and this code has so much packed in it.

In this example,

	(
		\value_bus: value, \delta_bus: delta, 
		\midi_func: func, \synth_instance: synth, 
		\free_me : {|self| self.do{|i| try {i.free}{} }; "free'd" }
	)

What would you call this programming construct? In the help file, anything within ( ) is an event. But in this case, itā€™s in a function denoted by {}, and it seems to function as a member function/variable of an anonymous class.

Nice call with the Slope. I didnā€™t know about that one. Iā€™m going with my diffnumber implementation. The original use case was to pair up a rotary with mouse movement. So what happens is, letā€™s say theyā€™re used to control frequency, thereā€™s a design choice to make whether the rotary is an absolute position and the mouse in absolute terms shifts the frequency up and down from this point, or each moves the frequency relatively from where their positions are now. I went with this because itā€™s a more interesting problem, and because the mouse will be used on other synths based on which synth is active, so an absolute position isnā€™t ideal because switching between synths causes large jumps.

Your right itā€™s an event and does function as a class/type like thing.

Key value pairs can be used to implement types. In python, if you dig around in all the ā€˜dunderscoreā€™ members you can actually find the underlying key value structure. Thatā€™s how they do meta programming.

Here, in supercollider, Iā€™m returning, what I like to think of as, an object. Itā€™s more complex than that - have a look at event prototyping. Personally, I find event prototyping too complex to be useful in itā€™s entirety, but for small objects with no inheritance, it works simply.

When you have a function inside of an event, the first argument is always the event itself. You donā€™t have to supply the event when you call it, the interpreter places it there for you. This is how other languages work too, python has a ā€˜selfā€™ argument, c++ a ā€˜thisā€™ā€¦ etc. You can then access the members of the object with square brackets, or a ā€˜.ā€™ call.

This is particularly useful when you will need to free multiple resources all at once.

I was trying to see how far this construct would work as an anonymous class, so something along the lines of

~aclass={
 | aexpr | 
 var k=Array.new();
 (\array: k, \addme:{ | self me | ( "output: " ++ self).postln ; k=k.add(me);} );
};

g=~aclass.(23);
g.addme("fjfj");

The addme function adds to k within the { | self me | ā€¦} block. There was an instance when doing g.array=g.array.add(23) for a couple of times causes the interpreter to crash. Sometimes the post window shows something being overflowed. I was hoping the { var ā€¦ } would keep the k in scope for functions in the event ( ), but it doesnā€™t seem to be the case. Still, interesting find there.

Look closely:

g = (array: [1, 2, 3]);
g.array;

Now what do you expect to get? I guess you expect [1, 2, 3].

Butā€¦

-> [ array, [ 1, 2, 3 ], nil, nil ]

When object prototyping, you must avoid methods already implemented on the environment object.

g.class.findRespondingMethodFor(\array)
-> Set:array

Soā€¦ oops. array is not a safe name to use.

So youā€™re modifying the environmentā€™s internals in an unsafe way ā†’ crash.

hjh

Modified this to xarray to avoid a nameclash, but g.addme(ā€œwerā€) doesnā€™t add to the { var k }, indicating the scope of the var element doesnā€™t include \addme .

Names with underscores (snake case) are a good idea as supercollider doesnā€™t use them.

@jamshark70 do you think it would be possible to raise an error when trying to set one of these preexisting names?

Thereā€™s no way to distinguish between a legitimate call to the preexisting method and a call to the wrong side of a name collision. Or, if youā€™re creating the event/environment, we donā€™t know how the object will be used. If youā€™re using it as an object prototype, then e.g. array would be questionable as a member name (though the user might have handled it well ā€“ the mere use of a method name as an object prototype key doesnā€™t inherently mean itā€™s bad). But if itā€™s not being used as a prototype, then there should be no restriction on the keys.

(
SynthDef(\notes, { |array = #[100, 200, 300, 400, 500], amp = 0.1, out|
	var sig = SinOsc.ar(array).sum * (0.2 * amp);
	Out.ar(out, sig)
}).add;
)

(
(instrument: \notes,
// this is called array, but it's not going to cause a bug
array: [{ exprand(200, 800) } ! 5],
amp: 0.7
).play;
)

One solution is to split object prototyping off into a separate classā€¦ which I did in the ddwProto quark. If youā€™re using a class called Proto, then itā€™s reasonable for the implementation to assume that you mean object prototyping. Then, in doesNotUnderstand:

			selector.isSetter.if({
				selector = selector.asGetter;
				(warnOnAssignment and: { super.respondsTo(selector) }).if({
					"'%' is already a method for Proto. Conflicts may occur."
						.format(selector).warn;
				});
				this.put(selector, args[0]);
				result = this
			}

(Old code, where I was still using a dumb way of writing ifā€¦)

But in Event / Environment, this should not be assumed. (So, one could argue that it wasnā€™t an ideal design decision to press Environment into service as an object prototype.)

This comes up from time to timeā€¦ somebody gets confused about something and then thereā€™s a proposal to detect the condition within the language and preemptively warn the user. I think we have to be very careful about that. Itā€™s very easy to introduce bugs and degrade performance.

hjh

That Proto class should be standard.

I stopped using event prototyping (switched to writing class) for this exact reason.
Any chance of it getting added?

Its not so much about confusion here. I know that this can happen, but there isnā€™t a reliable way to tell if it will be an issue (with doing g.class.findRespondingMethodFor(\array) for every method is very unergonomic). I think having the raw event be ambiguous is fine, so long as there is a way that you can opt out of that behaviour and just get exactly what you write (or throw if invalid). Your Proto class sounds perfect for this?

Hmm, Iā€™m not quite sure what you are trying to do here.

Inside of the \addme function you are reassigning k to the new result. This should not crash - if it is, its a problem with the garbage collection.

However, what you probably meant to do was assign to self[\my_array].

~aclass = {
	var k = Array();
	
	(
		\my_array: k, 
		\add_new_element : { |self, v| 
			self[\my_array] = self[\my_array] ++ v 
		}
	)
};

Which now works as expected.

x = ~aclass.()

x.add_new_element(1);
x.add_new_element(2);
x.add_new_element(3);

x.add_new_element("asd")
/// returning: ( 'my_array': [ 1, 2, 3, a, s, d ], 'add_new_element': a Function )
1 Like