How to link a gui slider with a midi controller?

I’d thought I’d leave this idea here to help and for me to refer to. I was wondering how to link a gui slider on screen and a rotary on a midi controller. To do this in supercollider has some quirks, like the synth cannot have control statements with if, etc. So I came up with this:

SynthDef(\twoinputs,{
 arg ib1,ib2,ob1;
 var i1.i2,t1,t2,r1,r2,sig;
 i1=In.kr(ib1);
 i2=In.kr(ib2);
 t1=HPZ1.kr(i1);
 t2=HPZ1.kr(i2);
 t1=t1.abs.ceil; // the trigger
 t2=t2.abs.ceil; 
 r1=SetResetFF.kr(t1,t2)*i1;
 r2=SetResetFF.kr(t2,t1)*i2;
 sig=r1+r2;
 Out.kr(o1,sig);
}).add;

a=Bus.control();
b=Bus.control();
c=Bus.control();
Synth(\twoinputs,[\ib1:a,\ib2:b,\ob1:c]);

Map the argument of a synth to c, create a gui slider with an action to set the midi controller value, and set the mididef to update the gui slider, and they will mutually update each other. The HPZ1 checks if there’s a change from the previous value, the ceil creates a 1 on any change, and the SetResetFF mutually toggles accepting the input bus that just moved.

1 Like

As an alternative, you may want to take a look at a quark that aims to provide this functionality.
(Disclaimer: I made it)

: )

Yes mine has a task that constantly polls to update the values as well. I guess what I need now is a timeline scrubber which displays the waveforms as well.

I’ve a mute button too! Great minds do think alike!

I’ve also got a group button, to gang a bunch of widgets together so a single master widget will move everything, and a mouse movement controller, and a x,y,xy,+ve,-ve direction changer. That way if the user gangs a bunch of parameters, everything changes at the same time. It runs through an ambisonic encoder+decoder as well.

Some surprising sounds there, though it frequently maxes out the entire audio channel.

Technically, my quark does not do any polling. It’s reacting to events.

Yes nice, i see you went with the singleton dispatcher model. I’m not sure why i didn’t consider this method before, but I was fixated with using busses for everything.

I would think that setting .drawFunc of a UserView to poll MIDI controller values and then set a slider position or rotary value based on the polled values would be the way to go. It could just be done with a routine, but since it’s already in a GUI, why make another routine?

Then you just have to decide how the interaction between setting a physical controller vs the GUI works unless you have motorized controllers. I’m guessing you already have this planned out though.

Basically there’s a slider, a readout, a mouse and a rotary. What happens with midi is that the midi function is called when a rotary moves, so polling the controller for a value isn’t a valid action to take. And it means there’s 3 methods of causing a change in the system, slider movements, a mouse movement and a rotary movement. To make it more fun, a master slider can be used to affect groups of parameters as well. What happens in gui slider movements is the gui event propogates changes through a chain of synthdefs. The new task thread is for updating the static text readout. The complicated bit is choosing which input method to use, so that’s what I posted.

Using a dispatch pattern should be more straightforward to implement. Register the widgets that need updating and hook all inputs into the dispatcher. But that didn’t cross my mind. I was thinking in terms of busses, and chaining nodes of transformations that would eventually be mapped to a synth parameter. The other choice that was difficult to make was deciding if the mouse would work in absolute coordinates or incrementally. If absolute, then the XY position would be lnlin to the synth parameters, but incrementally then an accumulator for the HPZ1 differences would be needed. The reason that makes sense is there are buttons to disable mouse inputs to the parameters, and absolute movements would mean large jumps if the parameters were reenabled. So far it’s been an interesting challenge in thinking about how these input methods interact with each other. It’s taken longer to reason out than i thought it would though.

now to actually go about and make some music

This situation – a single value with multiple UI representations – really calls for Model-View-Controller design. It takes a while to fully understand the design, but the payoff for understanding it is that, thereafter, you avoid many types of design mistakes and you can move more quickly toward a complex GUI and have fewer bugs along the way.

(If you’re trying to hack up the design by yourself, you’re unlikely to improve on MVC accidentally – trust me, it’s better to start with a solid design and adapt where needed.)

Last summer, this question came up and I wrote up a quick MVC demo: Good practice with repetitious GUI design - #2 by jamshark70 – which means I don’t have to rewrite it here :wink:

One of the main design points is that you need an object to represent the value. You said “the midi function is called when a rotary moves, so polling the controller for a value isn’t a valid action to take” and this is true – polling a control device is a bad idea. A good idea is to have an object that holds the value – the Model. Then, anywhere in the code that has access to the Model object can read the current value from this object.

The Model is distinct from all control devices (including UI widgets).

Control devices simply, and only, set a new value in the Model. Control devices should not directly affect any other control device.

The Model broadcasts an update (.changed) – so it doesn’t have to know which GUI(s) is/are listening to it. “Controller” objects (“controller” here as an element of the design, not as a physical control device) register to receive these updates, and update the graphical display. If you are sending data to a MIDI controller, then… yep… the same design applies: a MVC Controller gets updates from the Model and sends MIDI.

For a long time, I’ve thought that GUIs should only be shown on top of functionality that is completely implemented in terms of code calls. GUIs (IMO) should never serve as essential glue. When you wrote “create a gui slider with an action to set the midi controller value, and set the mididef to update the gui slider, and they will mutually update each other,” IMO that’s a design error. A better design would be:

  • MIDIdef calls value = on the Model.
  • GUI slider calls value = on the Model.
  • Model broadcasts and both the MIDI and GUI MVC Controller objects receive and forward.

Now, you can programmatically change the value from anywhere, with or without a GUI (and the GUI will update when it’s present).

Again, my opinion, take it or leave it, but I’ve been doing this for a long time… IMO, if it’s a very small, simple GUI, it’s fine to hack up a simpler design, but when things get more complicated (such as in my system, where I have an onscreen window with sliders being mirrored in an Open Stage Control interface on a tablet), if you want things to work cleanly, start with a solid design.

hjh

Yes I’m all for MVC. The prototype dependents notification is a great mechanism to set up a listener/observer type pattern. I didn’t know supercollider had that. One thing I’d like to point out, is it would be nice to have this as part of the ide tutorial.

The tutorials and the general form in which the language is introduced led me to believe that dataflow was mainly through bus mappings of control or audio signals. A consequence of which is some possibly convoluted ways in thinking how to transform data as it passes from controller to the synth parameter. In any case I didn’t have an overall design to begin with, partaking in the age old tradition of ball of mud engineering, where I first started out with wanting to control multi parameters with mouse gestures, to wanting to map midi controllers. It’s been really fun.

Yes, it is! However, be aware that the dependency notification system is separate from object prototypes. changed and update are defined on Object, so this protocol is available to every object. (I used a prototype in that example because I didn’t want to define a class only for this example, and then have to explain how to install the class.)

If you mean a tutorial chapter on basic GUI usage, sure. If you mean MVC, I think that’s a bit advanced for the “getting started” tutorial. It would be good to have a Guide-type document about MVC, and link to it from a “basic GUI” tutorial page. (We are pretty good about writing help for classes. The idea of writing “topic” documentation hasn’t really taken root though.)

I think a larger number of examples just set argument values directly…?

In any case, one thing that ended up making things easier for me (though at the time, I didn’t fully predict it) was that early on, I made a class that 1/ represents a value (with a ControlSpec) and 2/ syncs this value over to a control bus (which supports automation “for free”). This was originally for my Voicer class, where I wanted e.g. filter frequency to act like a typical hardware or software synth (all notes follow the control), but I ended up changing the hierarchy to put a freestanding GenericGlobalControl at the top.

At that time I didn’t know MVC, but then when I started looking at that, GenericGlobalControl / VoicerGlobalControl turned out to be a good Model for a single control. The way I’m using changed in these classes is a bit weird… I thought it was a good idea at the time; now I’m not so sure. But the following for instance is a legit MVC.

s.boot;

// example uses the ddwCommon quark (I think only this one)
~pan = GenericGlobalControl(\pan, nil, 0, \bipolar);

(
var slider = Slider(nil, Rect(800, 200, 200, 50)).front;

var updater = { |obj, msg|
	switch(msg[\what])
	{ \value } {
		defer { slider.value = ~pan.spec.unmap(~pan.value) };
	}
	{ \modelWasFreed } {
		~pan.removeDependant(updater);
	}
};

~pan.addDependant(updater);

slider.action = { |view|
	~pan.set(~pan.spec.map(view.value))
};
slider.onClose = { ~pan.removeDependant(updater) };
)

(
p = Pbind(
	\instrument, \default,
	\freq, Pexprand(200, 800, inf),
	\dur, Pexprand(0.1, 0.4, inf),
	\legato, Pexprand(0.3, 4, inf),
	\pan, ~pan.asMap
).play;
)

// `watch`: sync LFO values back to the language side
// ... which fires off a dependant notification
// so the GUI picks it up "for free"
~pan.watch.automate { LFDNoise3.kr(LFDNoise3.kr(0.25).exprange(0.7, 14)).range(-1, 1) };

p.stop;

~pan.stopAuto.stopWatching;

~pan.free;

I’m rambling about that just to point out that an early object-modeling decision made some problems go away in the long term.

hjh

1 Like

And … the end result of linking a gui slider with a midi controller.