Help: Using a MIDI knob and EZKnob to set the value of a pattern key

Hi everyone!

I’d like to use a MIDI controller knob to control an EZKnob that sets dinamically the value of the \freq in a pattern.

Can someone help me?

Thanks in advance :smile:

Here my code (//??? = big doubts!) :

(
MIDIClient.init ;
MIDIIn.connectAll ;
)

(
SynthDef(\stdSD_PatternMIDI, {
	arg freq = 50, atk = 0.01, rel = 1,
	vol = -12, pan = 0, out = 0 ;
	var sig, env ;
	sig = SinOsc.ar(freq, 0, 1) ;
	env = EnvGen.kr(Env.perc(atk, rel), 1, doneAction:2) ;
	sig = Pan2.ar(sig, pan, vol.dbamp);
	sig = sig * env ;
	Out.ar(out, sig) ;
}).add ;
) ;

(
var window, knob, pattern, spec, pattFreq ;

window = Window("midi", Rect(0, 0, 500, 500)) ;

knob = EZKnob(window, Rect(0, 0, 100, 100),
	label:"Freq", controlSpec: spec, action:???) ; // ???

spec = ControlSpec(minval:50, maxval:20000) ;

window.front ;

pattern = Pdef(
	\stdSD_PatternMIDI1,
	Pbind(
		\instrument, \stdSD_PatternMIDI,
		\dur, 1,
		\freq, Pfunc({pattFreq}), // ???
	) ;
).play;

pattFreq = MIDIdef.cc(\knob1, {arg value;
{knob.valueAction_(spec.map(value/127))}.defer
}, 1) ; // ???

)

It’s not a very good design to send midi to a gui object, and this gui control something else. The most common way to do it is to find a model-view-controller design. Take a look at SimpleController and Connection quark, for example.

The basic built-in template to do it just with core lib is something like this:

~alertSystem = Object();
~observer = { "I've been notified!".postln };
~alertSystem.addDependant(~observer);
~alertSystem.changed(\key, "value");
~alertSystem.removeDependant(~observer);

~alertSystem represents the model, managing the data and notification logic.

~observer is akin to the view, receiving updates and responding to changes.

1 Like

This situation is so common, and people often get confused with it, that probably we should find an easier way by default to deal with it. Maybe an opportunity for enhancement in the language and/or documentation here?

1 Like

I’m also curious what the simplest “best practice” MVC setup here is…

This is one way…

(
~model = (
  pattFreq: 100
);
~modelSet = { |field, value|
  ~model[field] = value;
  ~model.changed(field, value);
};
~pattern = Pdef(
	\stdSD_PatternMIDI1,
	Pbind(
		\instrument, \stdSD_PatternMIDI,
		\dur, 1,
		\freq, Pfunc({ ~model[\pattFreq] })
	) ;
).play;
)

this is just a bit of extra infrastructure so that when you change the model like so:

~modelSet.(\pattFreq, 600);

you can register dependants as smoge describes:

(
var window, knob, spec, updateFunc;

spec = ControlSpec(minval:50, maxval:20000, warp: \exp);

window = Window("midi", Rect(0, 0, 500, 500)).front;

knob = EZKnob(window, Rect(0, 0, 100, 100),
  label:"Freq", controlSpec: spec, initVal: ~model[\pattFreq], 
  action: { |obj| ~modelSet.(\pattFreq, obj.value) });

// this is what will update the GUI whenever the model changes
updateFunc = { |model, field, value| 
  defer {
    if (field == \pattFreq) { knob.value_(value) } 
  };
};

~model.addDependant(updateFunc);

window.onClose_({ ~model.removeDependant(updateFunc) });
)

and you can test it by again setting the model’s value manually:

~modelSet.(\pattFreq, 400);

which you should see reflected in the GUI knob. So now MIDI is easy:

(
MIDIClient.init;
MIDIIn.connectAll;

MIDIdef.cc(\knob1, { arg value;
  ~modelSet.(\pattFreq, value.linexp(0, 127, 50, 20000));
}, 1);
)

hope this is helpful…

2 Likes

Thank you @Eric_Sluyter ! It works very well!

Where did you learn all these useful things about MVC?

Glad to hear! I think I probably learned about MVC reading “the supercollider book” back in the day (it is now a bit outdated but still a useful resource) – actually noticing now that there is a “simple MVC example” in chapter 9 of the book that is basically the same as the code I gave you:

1 Like

I think it’s true most users come across these challenges at some point in their SC journey. So did I.

I’m not trying to push my quarks as the ultimate solution to these problems, but perhaps they can inspire someone to come up with something (or help you get something done quicker). They do hide a bit what is happening under the hood, so not ideal from a learning perspective.

In my quark GitHub - shimpe/scparco: Quark providing parser combinators for supercollider I’ve added a (toy, but working) example of a “text to ui” system. Using such system, one can simply “draw” the desired ui as ascii graphics (following some strict rules as it’s not an AI system :)). The point of mentioning this here is: the UI it generates from the text is fully MVC enabled so your code can observe/react to changes done by end-users to the ui controls and the ui controls themselves can observe/react to changes made to the underlying model.

If we are talking about midi, another interesting quark could be GitHub - shimpe/sc-midi-controls: bidirectional learning midi controls for supercollider which includes a MidiKnob control that can also react to changes in the ui by sending out midi, and observe changes coming in via midi by updating the ui. A MidiKnob also has built-in midi learning for helping with automating your control surface/synth (a knob can also be prebound to react to some incoming midi information, so you don’t absolutely have to use midi learning if you don’t want/need it).

1 Like

Maybe an updated version of this code (or based on this chapter, or a collection of different sources) could be included in the documentation, or is it already there and I missed it? Maybe a help document dedicated to this subject.

To check if it’s MVC, see if changes in the view cause problems in the model or if changes in the model disturb the view. Ensure the controller is the only one managing communication between the view and model. If everything works well with the controller handling the communication and no issues arise between the view and model, your design fits the MVC pattern.

Another aspect that the quark Connection raises, for instance, is scalability, if the software design allows working with many elements in several directions.