Noob: Storing & recalling "synthesizer patches"

I have a SynthDef, which is used to create synth(s) on the server. I am controlling the arguments of these synth(s) via MIDIDef(s) by turning encoders on a hardware MIDI controller box. The MIDIDef(s) also send MIDI data back to the MIDI controller box, so that the LED rings around the encoder show the adjusted parameter value.

While I am turning those encoders, I will find a sound I like. How can I store and recall these sounds, i.e. all the parameter values I dialed in via the encoders? And how would I load these patches and send the included parameter values to both the synth(s) and the controller box?

Thanks much in advance!

Hey, when using Ndefs and https://github.com/supercollider-quarks/JITLibExtensions you can save presets.
Maybe consider wrapping the SynthDefs into Ndefs with the MIDISynth Class Video Tutorials on MIDI based music production with Supercollider, jackd and DAW - #10 by droptableuser
@alikthename is explaining how to save the presets at around 1:00:00 in this video 18: MIDI controlled instrument with FX cascade and GUI - Musical Sound Design In Supercollider - YouTube

1 Like

Thank you very much – I will read up of Ndef, NodeProxy and JITLib and watch the videos!

Which controller are you using? I’ve got a few LED ring encoder controllers that I use, happy to share code if it would help.

1 Like

Check out the get, and getn, messages for Synth. You should be able to query your synths with these.

a = Synth(\runningSynth)
a.get(\freq, { |val| val.postln })

// or:
a.getn(0, lastArg, { |val| val.postln })

The first to arguments for getn set the start index, and amount of arguments from the start point to return.

1 Like

Thank you for your offer, I am using a MIDI Fighter Twister. I wrote the code below which works. I guess it is not the most elegant way to do it, but it does the trick. Would you mind having a look at it and let me know if there are better ways to do it?

Some things that came to my mind:

  • Is there a better way to alternate between 0 and 1, which I use as an index for an array with two values, holding the current value and the previous value, so that I can compare both values in order to decide whether a value change has happened (which would require send a MIDI value to the encoder’s LED) or not.
  • As I need an index variable i for every MIDIdef, how can I make sure that this i is only valid for this specific MIDIdef? While just putting {} around code below did not do the trick.
(
z=64; // Start value for parameter
i=0;  // initialize index for comparing previous and current z value
~change=Array.newClear(2); // initialize array holding previous and current z value

MIDIdef.cc(\cc0_cutoff_dec_inc, {
	|val, num, chan, src|
	val = val - 64; // turn encoder value into value change
	z = z + val;    // use value change to change parameter
	z = z.min(1024).max(0); // limit parameter range to 0 and 1024

	// turn parameter range into MIDI range for LED ring
	// put MIDI value at current index of comparison array
	~change.put(i, z.linlin(0,1024,0,127).round(1));

	// compare value at current index of comparison array with value at previous index of comparison array
	// only send current MIDI value to LED ring if current value differs from previous value
	// needed to avoid clogging MIDI by sending already sent values again
	if (~change[i] != ~change[(1-i)], {m.control(0,0,~change[i])});

	i = (1 - i); // alternate index for comparison array

	x.set(\cutoff, z.linexp(0,1024,100,10000)); // send parameter value to synth stored in variable x
}, 0, 0).permanent_(false);
)

Thank you, I will try these out. I guess the most puzzling thing for me right now is: Once I have the parameter values, how do I store them on disk? And how do I load them?

Ignoring the MIDI controller for a sec, one simple way to store presets isn’t on a disk at all, but with the SynthDef, as a variant. This will let you name the presets too, and call them by name.

I like storing them this way, because they’re saved with the SynthDef, kind of like a synth patch would be on a synthesizer.

Depending on how you have your MIDIdefs set up, you might be able to take all the values stored in the variant and feed them back into the controller to initialize its values and update the LEDs. Or if you’re storing the values from the MIDIdefs in an array or elsewhere, you can overwrite those initial values based on the variant’s values.

1 Like

Thank you for the variant tip! But from reading the documentation it looks like the content of a variant can not be changed from outside the SynthDef, i.e. I can only change a variant by editing its code inside the SynthDef, right?

There is a .variant = part in the helpfile, but I’m not sure if it’ll work for creating new ones, I haven’t tried. So, yes, I’m pretty sure you’ll need to add them manually to the SynthDef and recompile.

To answer your general questions first…

Is there a better way to alternate between 0 and 1, which I use as an index for an array with two values, holding the current value and the previous value, so that I can compare both values in order to decide whether a value change has happened (which would require send a MIDI value to the encoder’s LED) or not.

I think the way you are checking historical values is fine, if you find the code clear for yourself (I’ll usually implement this as a kind of if (newValue != value) { value = newValue; sendUpdate() } thing, but it’s functionally pretty identical to what you’re doing).

I would question whether it’s worth worrying about not sending an update in case the value does not change - I seem to recall that on every MIDI encoder device I’ve used, sending an identical value BACK to the device when a MIDI cc is received works without any problems. Without this, your code gets much simpler. It’s not likely to be worth it to optimize out one channel of MIDI sends - the most computationally expensive part of what you’re doing is almost surely the checking code and not sending a MIDI message.

  • As I need an index variable i for every MIDIdef, how can I make sure that this i is only valid for this specific MIDIdef? While just putting {} around code below did not do the trick.

I would suggest storing values in an IdentityDictionary - it’s easy to use either the CC number or the MIDIdef name as a key, e.g.

~values = (); 
// in MIDIdef...
if (value != ~values[\cc0_cutoff_dec_inc]) { 
  ~values[\cc0_cutoff_dec_inc] = value; 
  doUpdateStuff();
}

If you don’t want to roll your own MIDI interface, you might try this quark: https://github.com/scztt/Twister.quark (Quarks.install("https://github.com/scztt/Twister.quark")).

An example linking some Twister knobs to buses, and then using the buses in a pattern:

(

////////////////////////////////////////////////////////////
// These will be my control values
~c = ~c ?? { ControlValueEnvir() };

// Set up my controls
~c.use {
	~detune.spec = ControlSpec(-4, 4);
	~amp.spec = ControlSpec(0, 1);
	~dur.spec = ControlSpec(1/8, 4, \exp);
};


////////////////////////////////////////////////////////////
// Create a Twister
// Device is expected to have this naming:
//    <endpointDevice="Midi Fighter Twister %", <endpointName="Midi Fighter Twister";
// ...where % is 1 or 2. This can be set in Audio MIDI Settings on mac.
~t = Twister(\primary);

// Connect knobs to controls
~t.knobs[0].knobCV = ~c[\detune];
~t.knobs[1].knobCV = ~c[\amp];
~t.knobs[2].knobCV = ~c[\dur];

~t.knobs[3].buttonCV.signal(\on).connectToUnique({
	Pdef(\pat).set(\scale, Scale.lydian)
});
~t.knobs[3].buttonCV.signal(\off).connectToUnique({
	Pdef(\pat).set(\scale, Scale.aeolian)
});

////////////////////////////////////////////////////////////
// Connect controls to a pattern - these can be used directly as synth args also
Pdef(\pat, Pbind(
	\amp, ~c[\amp],
	\dur, ~c[\dur],
	\detune, ~c[\detune],
	
	\octave, 4,
	\strum, 1/8,
	\degree, [0, 4, 2, 8, 7]
).trace).play;

)

Some caveats …

  • I have used this class for years, but strictly speaking it’s beta and may have some issues.
  • It’s also undocumented, but should support basically every feature of the device, including led colors, side buttons etc.
  • iirc you need to configure your Twister to send relative messages, with cc numbers starting from 0.
  • happy to answer questions
1 Like

Thank you very much for your answer and the example – it will take some time for me to digest these.

Haha, I was so proud to have found a method to “thin out” the MIDI stream that I have to admit I am having a somewhat of a hard time letting go of it. But thinking of it – I can only turn 2 encoders simultaneously, so the resulting amount of MIDI messages will still be so comparatively small that I better let go of the optimization code. But thanks for the alternative way of comparing old & new value, this is much more elegant, and I am sure there will come a point in my SC journey in which I will use your method!

I could not resist to try to “roll my own interface” for MIDI Fighter, but in trying to do so, I am stuck with the following:
I want to have a variable that is local to a MIDIdef function,
so I declare it,
this variable gets assigned a value when the MIDIdef function is called,
and on the next call of this MIDIdef function I want to use this variable’s value it got on the previous call of the function,
but as the variable is declared, it always has the same value.

Please, what am I doing wrong?

The variable cannot be local to the MIDIdef function, then – but it can be local to a function that prepares the MIDIdef for you.

~makeMIDIdef = { |type = \cc, name, func, srcID, other parameters|
    var localThing;
    MIDIdef.perform(type, name, { |val2, val1|
       ... do stuff with localThing...
       if( condition involving localThing ) {
            func.value(val2, val1)
        };
    }, other parameters...);
};
)

… assuming here that the localThing is being used for filtering. If localThing is to be used inside the user-action func, then you’d have to pass it in as an argument, and the func should return the new localThing value. Something like that…

hjh

1 Like

Thank you very much, this is very helpful – I will try it out asap, too bad the weekend is over over here!

May I ask another question in this context? I just can’t get my head around the difference between a string and a symbol. Here’s an example that bamboozles me:

This here works:
~midiFighter_encoders[0].at(\parameter); // returns freq
~init_sound.at( \freq ); // returns 50

So I thought this here would also return 50, but no:
~init_sound.at( ~midiFighter_encoders[0].at(\parameter) ); // returns nil

In order to make it work, it must be a symbol:
~init_sound.at( ~midiFighter_encoders[0].at(\parameter).asSymbol ) // returns 50

But when I look at the results of both variants in the post window, they look identical:
~midiFighter_encoders[0].at(\parameter).postln; // freq
~midiFighter_encoders[0].at(\parameter).asSymbol.postln; // freq

Please, what is the difference? What makes a symbol a symbol, and how can I differentiate it from a string?

You should post the part of your code where you declare ~midiFighter_encoders and ~init_sound.

I had to make a few assumptions about what types of objects these were, and I assumed they’d be Events or IdentityDictionaries. With either, I was able to make your example work as you expected it to:

~midiFighter_encoders = [(), (), (), ()]
~midiFighter_encoders[0].put(\parameter, \freq)
~midiFighter_encoders[0].at(\parameter); // returns freq
~init_sound = ()
~init_sound.put(\freq, 50)
~init_sound.at( \freq ); // returns 50

~init_sound.at( ~midiFighter_encoders[0].at(\parameter) ); // returns 50!!

Perhaps you entered ~midiFighter_encoders[0].put(\parameter, "freq") instead? In which case it makes sense that you received nil.

The big difference between symbols and strings is the difference between “equality” and “identity”. Two symbols with the exact same set of characters are both equal and identical to one another, two strings are equal, but not identical:

\test == \test // test equivalence: returns true
\test === \test // test identity: returns true
"test" == "test" // test equivalence: returns true
"test" === "test" // test identity: returns false

There was a good thread on this recently: Arrays by Reference (?) The put method

I often use the class message to verify things when I get unexpected behavior.

Eg, evaluating ~midiFighter_encoders[0].at(\parameter).class returns Symbol in the post window for me. Yours likely returns something different.

1 Like
x = "freq";
y = x.asSymbol;

x.postcs;  // "freq"
y.postcs;  // 'freq'

x.asCompileString;
-> "freq"

y.asCompileString;
-> 'freq'

// functional difference between strings and symbols:
// strings can be equivalent (==) but not identical (===)
// for symbols, equality and identity are the same
x === x.copy  // false
y === y.copy  // true

hjh

1 Like

Thank you very much, @thresholdpeople & @jamshark70, .class and .postcs are very helpful for me!

That’s exactly what I did – I assumed (for reasons I can’t explain, now that I think of it) that I am not allowed to use keys as values.

I will read the thread you recommended!