How to structure numerical calculations/simulations in SC

Hi community! All new here and to SC

I come from applied physics and I’m used to using python for scientific programming.

I have this idea for using a numerical emulation of waves on a membrane as a kind of advanced LFO, and thought I would give SC a spin. I’m really confused. I’ve followed some tutorials but they all approach the language from a kind of “Lets make music” viewpoint. I would rather like to learn it from a “Lets make math” viewpoint so to speak. (Any tips on where to find this is much appreciated).

Anyway, I’m confused as to how the for loops and while loops (and do? New to me) seem to require functions within them, inside which only variables defined in these inner “loop”-functions can be used. Passing variables in and out of loops to be able to access them seems like a real hassle? While working with global variables required to have single letter names also seems like a wierd arbitrary work around.

In meta code written in a mashup of python and SC, the membrane part works like this:

endTime = 50;
n = 100; //gridsize
timeStep = 0.5;
roomStep = 1;
speed = 1;
xSum = 0;
ySum = 0;
t = 0;

currentGrid = Array2D.fromArray(n,n);
oldGrid = Array2D.fromArray(n,n);
newGrid = Array2D.fromArray(n,n);

while t < endTime:
– for i in range(0,n):
---- for j in range(0,n):
------ xSum = currentGrid[i + 1, j] - 2*currentGrid[i,j] + currentGrid[i - 1,j]
------ ySum = currentGrid[i,j + 1] - 2*currentGrid[i,j] + currentGrid[i,j - 1]
------ newGrid[i,j] = 2*currentGrid[i,j] - oldGrid[i,j] + (((speed**2)*(timeStep**2)*(xSum + ySum))/(roomStep**2))

– oldGrid = Array2D.copy(currentGrid)
– currentGrid = Array2D.copy(newGrid)

– t++

After this the values in currentGrid are to be used in loops that generate synths. But one problem at a time.

How do you properly construct nested for loops and similar in SC?

Anyway, I’m confused as to how the for loops and while loops (and do? New to me)

number.do is basically the same as for(i = 0; i < number; i++). But do can also iterate automatically over arrays and other collections, or over streams.

seem to require functions within them

Yes.

inside which only variables defined in these inner “loop”-functions can be used.

Not true. Functions have access to any variables declared within their own scope or any enclosing scope.

{
	var x;
	10.do { |y|
		... you can use x and y here...
	};
	10.do { |z|
		... you can use x and z here...
		... but *not* y...
		... x is in the enclosing scope; y is not...
	};
}

Now… I suspect the next thing that’s going to trip you up is that an LFO is signal processing, but signal processing code is not written directly in the language as loops. Signal processing is done in UGens, which are written in C++ and precompiled.

You could use sc-language code to produce data, and you can do whatever you want with the data (including load it into a buffer and play it back in the server). But if you want to run the simulation in real-time and get some signal output from it, currently that requires C++.

hjh

Thank you for a helpful reply!

Hmm, LFO is a bit misleading. I would input values from the membrane as values into a new synth, where each synth would be one musical “event” (like just one note).

Okay good, was really confused about the scope, must have been some other error then

Though writing my own UGens in C++ sounds really interesting!

One thing you for sure will run into (well… at least I do, all the time) is the supercollider way of (not) handling operator precedence. Basically if you write:

1+2*3

supercollider will interpret it as

(1+2)*3

whereas a typical language would interpret it as

1+(2*3)

If you want supercollider results to make sense, think of adding enough brackets.

Hi,

if you’re happy with new data per event, have a look into SC’s Pattern system, which is designed for event sequencing. Calculating new grid values can be put into one of those Patterns which take Functions (Pfunc, Prout, Plazy atc.). As timing the events is done with latency and therefore exact, you don’t have to worry about calculation time of grids (well, depends on size of course, but 100 x 100 with time step 0.5 sec doesn’t sound problematic).

The number of input values (control inputs) of the synthdef could be a limiting factor, but I assume you would want to reduce synth data from membrane data (and/or you spread the data to masses of synths, which could be a proper strategy as SC server is very good in doing such).

Ah, yes new data per event is how I imagine it. Also the input values of each synth will be quite few (maybe four or five) but there will potentially be as many synths as there are grid points on membrane (one synth is created each time the derivative of point changes sign, playes one note, is destroyed).

Thanks for the tip on patterns! Such things as latency have not yet crossed my mind. This will be a learning experience for sure :slight_smile: