Converting multiple SinOsc to array and plotting them

Hi everyone, I’m very new to SC and I’m trying to figure out how to work with this amazing piece of software.

Ultimately what I want to do is to plot multiple (sine)waves on a single plot. I figured how to do it with array of floats, but not with SinOsc directly, so I’m trying to load float array from SinOsc:

 (
    f={arg l;
	~t=[[]];
	l.do({arg n,i;
		i.postln;
		{SinOsc.ar(220*n,mul:0.5)}.loadToFloatArray(0.01, action: { arg array;~t.add(array.flatten);});
		~t.do({arg i; i.postln;});
	};)};
    )
    f.value([2,3,4]);

First I was really confused at how it behaved (~x remains empty), but I figured loadToFloatArray is async, and my printout of ~x happens before anything is loaded into it, right?

Is there a way to wait for it to complete? I found Routines which seem to be related, but can’t figure how to use them here. Or is there a simpler way altogether?

Thanks!

Hello,

“.loadToFloatArray” needs some time to work. In your code, the part ~t.do({arg i; i.postln;}); calls “~t” after “~t” is initialised. However, that moment is still before “~t” is filled with the array by".loadToFloatArray" method.

~t.do({arg i; i.postln;}); should be placed at the last line of action as follows:

(
f={arg l;
	~t=[[]];
	l.do({ arg n,i;
		i.postln;
		{SinOsc.ar(220*n,mul:0.5)}
		.loadToFloatArray(0.01, action: { arg array;
			~t.add(array.flatten);
			~t.do({arg i; i.postln;});});
};)};
)

f.value([2,3,4]);

Please look at the following revision of your code. The Post window posts everything step by step according to the order of processed events:

(
g={ |harmonics|
	var summedArray = [[]];
	harmonics.do{ |nthHarmonic, index|
		"iteration index %:\n".postf(index);
		{ SinOsc.ar(220 * nthHarmonic, mul:0.5) }
		.loadToFloatArray(
			2/s.sampleRate,
			action: { |arrayOfNthHarmonic|
				var add;
				"\nloadToFloatArray index % started:\n".postf(index);
				add = summedArray.add(arrayOfNthHarmonic.flatten);
				"added index %:  %\n".postf(index, add);
				summedArray.do{ |array|
				"summedArray at the index %:  %\n".postf(index, array)
				};
		});
		
}}
)

g.value([2,3,4]);

For me, there is a surprise that index 0 is processed lastly, and index 2 is processed first in the “actoin” of the “.loadToFloatArray”. Why does it work in a reversed order?

iteration index 0:
iteration index 1:
iteration index 2:
→ [ 2, 3, 4 ]

loadToFloatArray index 2 started:
added index 2: [ [ ], [ 0.057468570768833, 0.11417542397976 ] ]
summedArray at the index 2: [ ]
summedArray at the index 2: [ 0.057468570768833, 0.11417542397976 ]

loadToFloatArray index 1 started:
added index 1: [ [ ], [ 0.057468570768833, 0.11417542397976 ], [ 0.043143179267645, 0.085964545607567 ] ]
summedArray at the index 1: [ ]
summedArray at the index 1: [ 0.057468570768833, 0.11417542397976 ]
summedArray at the index 1: [ 0.043143179267645, 0.085964545607567 ]

loadToFloatArray index 0 started:
added index 0: [ [ ], [ 0.057468570768833, 0.11417542397976 ], [ 0.043143179267645, 0.085964545607567 ], [ 0.028782008215785, 0.057468563318253 ] ]
summedArray at the index 0: [ ]
summedArray at the index 0: [ 0.057468570768833, 0.11417542397976 ]
summedArray at the index 0: [ 0.043143179267645, 0.085964545607567 ]
summedArray at the index 0: [ 0.028782008215785, 0.057468563318253 ]

The problem is, I want to plot summedArray, not print it out. But it’s empty when that code is reached. How can I wait for it to be initialised?

(
g={ |harmonics|
	var summedArray = [[]];
	harmonics.do{ |nthHarmonic, index|
		"iteration index %:\n".postf(index);
		{ SinOsc.ar(220 * nthHarmonic, mul:0.5) }
		.loadToFloatArray(
			2/s.sampleRate,
			action: { |arrayOfNthHarmonic|
				var add;
				"\nloadToFloatArray index % started:\n".postf(index);
				add = summedArray.add(arrayOfNthHarmonic.flatten);
				"added index %:  %\n".postf(index, add);
				summedArray.do{ |array|
				"summedArray at the index %:  %\n".postf(index, array)
				};
		});
		};
	~c = [Color.black, Color.red, Color.blue ];
	~p=summedArray.plot;
	~p.superpose_(true);
	~p.plotColor_(~c);
}
)

g.value([2,3,4]);

This code kinda works, but not within a function - I need to plot a global variable manually, which I don’t want:

~summedArray = [[]]; // if array is initialised as [], add doesn't work?
(
g={ |harmonics|
	harmonics.do{ |nthHarmonic, index|
		"iteration index %:\n".postf(index);
		{ SinOsc.ar(220 * nthHarmonic, mul:0.5) }
		.loadToFloatArray(
			2/s.sampleRate*100,
			action: { |arrayOfNthHarmonic|
				var add;
				"\nloadToFloatArray index % started:\n".postf(index);
				add = ~summedArray.add(arrayOfNthHarmonic.flatten);
				"added index %:  %\n".postf(index, add);
				~summedArray.do{ |array|
				"summedArray at the index %:  %\n".postf(index, array)
				};
		});
		};
}
)

g.value([2,3,4]);


(
~summedArray[0]=~summedArray[1]; //this is just a dummy copy as plot fails with empty first element
~c = [Color.black, Color.red, Color.blue ];
~p=~summedArray.plot;
~p.superpose_(true);
~p.plotColor_(~c);
)

The issue in your code is that loadToFloatArray is asynchronous. Basically, the .plot is happening before you action of the loadToFloatArray.

Would something like this work for your purposes or are you trying to do something more than just get the plot?

{
	a = SinOsc.ar(220*[2,3,4]);
	[a,a.sum].flatten
}.plot

Sam

1 Like

That wasn’t what you want. I think this is:

{
	Array.fill(4, {|i| Array.fill(i+1, {|i2| i2+1})}).collect{|notes| SinOsc.ar(220*notes).sum};
}.plot

Gotta give a presentation right now, but I can explain later tonight.

Sam

As @Sam_Pluta mentioned, the problem is the asynchronous execution of the code. There must be enough time between the start of the action of collecting data and the activity of plotting the data. Therefore, plotting should start after the process of collecting data is finished. Usually, it could be done by Routine class in SuperCollider and “fork” is a handy method for it.
Could you try the code below? You could see (timeNeeded * 50).wait;. You can change (timeNeeded * 50) if you want. On my end, sometimes (timeNeeded * 15) also works, but sometimes not. Expert programmers might replace xxx.wait with another way. In this case s.sync does not affect sadly. I will be happy if someone gives me feedback.
summedArray = [] works now:

(
h={ |harmonics|
	fork{
		var summedArray = [], freq = 220, timeNeeded = freq.reciprocal;
		
		harmonics.do{ |nthHarmonic, index|
			"iteration index %:\n".postf(index);
			{ SinOsc.ar(freq * nthHarmonic, mul:0.5) }
			.loadToFloatArray(
				timeNeeded,
				action: { |arrayOfNthHarmonic|
					var add;
					"\nloadToFloatArray index % started:\n".postf(index);
					summedArray = summedArray.add(arrayOfNthHarmonic);
					"added index %:  %\n".postf(index, add);
					summedArray.do{ |array|
						"summedArray at the index %:  %\n".postf(index, array);
					};
			});
		};
		(timeNeeded * 50).wait;
		{
			var colour, plotting;
			colour = [Color.black, Color.red, Color.blue ];
			plotting = summedArray.plot;
			plotting
			.superpose_(true)
			.plotColor_(colour);
		}.defer;
	}
}
)

h.value([2,3,4]);

I think your code is too complex. As @Sam_Pluta suggested, I usually do this as follows:

(
{ SinOsc.ar(220*[2,3,4]) }.plot(1/220)
.superpose_(true)
.plotColor_([Color.red, Color.blue, Color.black];)
)

If you want to see the sum of the all signals, what @Sam_Pluta suggested is the perfect one:

(
{
	a = SinOsc.ar(220*[2,3,4]);
	[a,a.sum].flatten
}.plot
.superpose_(true)
.plotColor_([Color.red, Color.green, Color.blue, Color.black];)
) 
1 Like

Great answer from @prko there. I love the superimposed different colors. I didn’t know you could do that.

See my version below where you can name which partials you want to plot. The trick is getting the array in the right shape so that you can sum each block of sine waves. This code makes a large array with elements that are each arrays with increasingly more partials in them (see the array that is printed via postln). Then it places each of these arrays inside a SinOsc and sums the result. It places each of the summed results into another array, which it plots.

~getRunningSums = {|partials=#[2,3,5]|
		var partialArrays = partials.collect{|partial, i| 
			Array.fill(i+1, 
				{|i2| partials[i2]}
			)
		};
		partialArrays.postln;
		{partialArrays.collect{|notes| SinOsc.ar(220*notes).sum}}.plot;
};

~getRunningSums.value([1,2,3]);
~getRunningSums.value([3,5,7,11,13]);

Sorry for the flashy array manipulation, but I think this is the best way to do this.

You may also want to look at Signal.sineFill:

[
	Signal.sineFill(440, [0,1]),
	Signal.sineFill(440, [0,1,0,1]),
	Signal.sineFill(100, [0,1,0,1,1])
].plot

Sam

1 Like

Thanks guys, with a combination of both suggestions I get a nice solution:

(
g={ |harmonics|
	var arr={harmonics.collect({|n| SinOsc.ar(220*n,mul:0.5)})};
	var plotting = arr.plot;
	plotting
	.superpose_(true)
	.plotColor_([Color.black, Color.red, Color.blue ]);
}
)

g.value([2,3,4]);

The key is to take the curly brackets out of collect and put them around, e.g. {harmonics.collect({|n| SinOsc.ar(220*n,mul:0.5)})}. This expression can be plotted directly. I don’t quite understand why (yet) to be honest, but it works :slight_smile:

1 Like

You found your solution amazingly quickly! A ugen will work when sclang requests it to a server; to do this, a language-side ugen is coded in a function, then it should be sent to a server by .play, or .scope; then its server-side ugen works. As far as I understand, the function method .plot needs a server to get output values from UGens, even though it does not produce sound.
Wrapping all necessary parts as a function may reduce functions and parenthesis pairs as follows:

(
g={ |freq, harmonics|
	{ SinOsc.ar(freq * harmonics, mul: 0.5) } 
	.plot(1 / freq)
	.superpose_(true)
	.plotColor_([Color.black, Color.red, Color.blue]);
}
)

g.value(220, [2,3,4]);

Just in case you didn’t see it yet, the Mix Ugen is very convenient when dealing with arrays of signals.
Here, you are losing the individual components, but can output the resulting signal without synchronicity issues :

({
	var harmonics = [2,3,4];
	
	Mix.ar(
		Array.fill(harmonics.size, {|harmonic| SinOsc.ar(220 * harmonic, mul: 0.5) })
	);
}.plot;
)

The downside of this is that Mix reduces the overall amplitude of the result (effectively mixing signals).