Cleverer way to mass produce variables for multiple SynthDefs

I want to be able to create, modify, and free multiple SynthDefs easily. I’ve struggled to find an elegant way to do this. I came up with this temporary solution which is definitely not elegant. I’ve tried multiple ways to generate new variable names without the need for an array but no luck so far!

Thanks, Dom

(
var names = ["a","b","c"];
3.do({ |i|
	names[i] = Synth(\basic, [\freq, 100 * i]);
});
)

Why don’t you want to use an array? That is the way to do it.

There is no reason to initialize the array the way you are doing it. Maybe you are looking for “collect”? Or just use a List and add each element to the list.

(
var names = 3.collect{ |i|
	Synth(\basic, [\freq, 100 * i]);
};
)
(
var names = List.newClear(0);

3.do{ |I|
names.add(Synth(\basic, [\freq, 100 * i]));
};
)
2 Likes

@domaversano I think instead of var names = ["a","b","c"];, var names = Array.newClear(3) would be better, as @Sam_Pluta did with List.


From the comparison of three mentioned ways, using .collect is the simplest one because it reduces 1 line. (I would use .collect)

  • Using Array
     (
     fork {
     	var names = Array.newClear(3);
      	
     	3.do { |i|
     	    	names[i] = Synth(\default, [\freq, 100 * i]);
     	};    	
      	
     	2.wait;    	
      	
     	3.do { |i|
     	    	names[i].set(\freq, 100 * i * 2);
     	}; 
      	
     	2.wait;    	
      	
     	3.do { |i|
     	    	names[i].release;
     	    	1.wait
     	}
     }
     )
    
  • Using List
    (
    fork {
    	var names = List.newClear(0);    	
     	
    	3.do{ |i|
    	    	names.add(Synth(\default, [\freq, 100 * i]));
    	};    	
     	
    	2.wait;    	
     	
    	3.do { |i|
    	    	names[i].set(\freq, 100 * i * 2);
    	};    	
     	
    	2.wait;    	
     	
    	3.do { |i|
    	    	names[i].release;
    	    	1.wait
    	}
    }
    )
    
  • Using .collect
    (
    fork {
    	var names = 3.collect{ |i|
    	    	Synth(\default, [\freq, 100 * i]);
    	};    	
     	
    	2.wait;    	
     	
    	3.do { |i|
    	    	names[i].set(\freq, 100 * i * 2);
    	};    	
      	
    2.wait;    	
     	
    	3.do { |i|
    	    	names[i].release;
    	    	1.wait
    	}
    }
    )
    

An efficient way would be:

(
var numSynths = 3;
fork {
	var names = numSynths.collect{ |i|
		Synth(\default, [\freq, 100 * i]);
	};
	
	2.wait;
	
	numSynths.do { |i|
		names[i].set(\freq, 100 * i * 2);
	};
	
	2.wait;
	
	numSynths.do { |i|
		names[i].release;
		1.wait
	}
}
)
1 Like

Maybe we need a help file, “How to mass-produce variables, and why you shouldn’t” :wink:

The only way to kinda-sorta meaningfully mass produce variables is with environment vars.

100.do { |i|
	("x" ++ i).asSymbol.envirPut(1000000.rand)
};

~x50  // ok

// get one randomly
("x" ++ 100.rand).asSymbol.envirGet

By contrast, if you use an array, creation is simpler: c = Array.fill(100, { 1000000.rand }); and indexed access is simpler: c[100.rand] instead of… do you really want to write .asSymbol.envirGet / ...Put every time?

The array is also about 8.5 times faster:

(
var size = 100;
var array = Array.fill(size, { 1000000.rand });
var env = Environment.make {
	size.do { |i|
		("x" ++ i).asSymbol.envirPut(1000000.rand)
	};
};

"array access".postln;
bench { 1000000.do { array[size.rand] } };

"environment access".postln;
env.use {
	bench { 1000000.do {
		("x" ++ size.rand).asSymbol.envirGet
	} };
};
)

array access
time to run: 0.043272551999507 seconds.
environment access
time to run: 0.37863402000039 seconds.

So, mass-producing environment variables gives you clunkier syntax and nearly an order of magnitude worse performance (edit: see note) – but it lets you once in a while write ~x50 instead of x[50]. I’m not exactly sold.

Edit: Note – the worse performance above is mostly because of the string concatenation and symbol conversion. Using your own handwritten symbols in a dictionary, Environment or Event is very fast – it’s just that it takes more work to use Symbol keys in conjunction with numeric indices, and that slows things down. So – numeric indices ↔ arrays, symbols ↔ dictionaries.

It’s possible to hack interpreter variables:

// in interactive code, 'this' is the Interpreter object
// set vars a through j
(
10.do { |i|
	this.perform(
		(97 + i).asAscii.asString.asSymbol.asSetter,
		100.rand
	)
};
)

j  // ok

But you only have 25 of those (if you respect the standard reservation of s for the default server), and again, the syntax… well, you could actually drop .asString because Char asSymbol goes through Object which automatically .asString-s it, but the flow is still the same: char → string → symbol → use in perform. Not sure it’s worth all that.

The only way to mass-produce var declarations is to make a string containing SC code and .compile or .interpret it – but those variables will be local to the compiled function, with no way to access them outside. So there’s not really much point, when it would be easier to just write the function in braces and use arrays.

hjh

2 Likes

the first two solutions above (and your original solution) do allow you to make and manipulate lots of Synths… but you are only accessing them by index, if you want to access them with names instead you want to use a Dictionary - or Event which gives you pseudo-methods…

(
var synths = ();
[\low, \med, \hi].do { |i| synths[i] = Synth(\default, [\freq, i* 100]) }
)

then you can access them by name synths.low or synths[\low]

to get a list of the synths call synths.values - for the names synths.keys

a cool bonus is that you can then write functions that act on all the synths (pseudo-methods):

synths.rand = { |self| self.do { |i| i.set(\freq, 400.rrand(800)) } }

here do acts on the values (the synths)

3 Likes

Yes! That proposed help file should recommend this over variables as the proper way to collect things by name, when it might be unknown in advance how many things need to be collected or what their names are.

hjh

Why don’t you want to use an array? That is the way to do it.

I can see this point now. All the addition, very helpful, replies show that my previous thinking creates a much more convoluted approach.

Maybe we need a help file, “How to mass-produce variables, and why you shouldn’t”

This. I also wonder if the SynthDef help file would benefit from an example of how to mass-produce SynthDefs and change their arguments while in use. Using one, or even just a few SynthDefs strikes me as quite a delimited use of Supercollider.

Thanks for everyone’s contributions, as always.