How to feed an array to a SynthDef

I’ve been reading, and re-reading the Pattern Guide document, but I’m quite confused with this.

I have an array of arrays. I want to be able to use those arrays to control the SynthDef.

The closest code I’ve seen to this comes from the SC book:

 Task({
                   inf.do({arg cnt3;
                       Synth("SimpleBlip",
                           [\midi, ~pSeq.wrapAt(cnt1).wrapAt(cnt3),
                            \tone, harmSeq.wrapAt(cnt3),
                            \art, durSeq.wrapAt(cnt3),
                            \amp, rrand(0.1, 0.3),
                            \pan, cnt1.wrap(-1, 2)]);
                            0.125.wait;
                     })}).start;

I have problems with this code, as since I’m returning arrays I believe I need to use pattern such as Pseq, as I’d like the option to repeat the array a few times. When I’ve simply typed in the arrays only the first value is played. Again, I think this is because I need to be using Pseq, or something similar to cycle through the values.

Apologies for the potentially basic nature of the question - any insight is greatly appreciated.

Here is a good introduction to SuperCollider and more specifically pattern:

Could you be a bit more descriptive? What are the nested arrays holding, are you trying to send arrays to a synth with set? What are the rest of your variables? Are you actually using Pseq here? Could you make a minimal example?

Using Task to build your own sequencer is usually not necessary (sometime it can be more flexible, but it takes a lot more effort that just using Patterns).

Also, this is how the code should be indented...
Task({
	inf.do({arg cnt3;
		Synth("SimpleBlip",
			[\midi, ~pSeq.wrapAt(cnt1).wrapAt(cnt3),
				\tone, harmSeq.wrapAt(cnt3),
				\art, durSeq.wrapAt(cnt3),
				\amp, rrand(0.1, 0.3),
				\pan, cnt1.wrap(-1, 2)]);
		0.125.wait;
})}).start;

…but this is preferable because conceptually you can fold up the indents a little clearer…

Task({
	inf.do({ | cnt3 |
		Synth("SimpleBlip", [
			\midi, ~pSeq.wrapAt(cnt1).wrapAt(cnt3),
			\tone, harmSeq.wrapAt(cnt3),
			\art, durSeq.wrapAt(cnt3),
			\amp, rrand(0.1, 0.3),
			\pan, cnt1.wrap(-1, 2)
		]);
		0.125.wait;
	})
}).start;

// folded 1
Task({
	inf.do({ | cnt3 |
		Synth( .... );
		0.125.wait;
	})
}).start;

// folded 2
Task({
	inf.do({ ... })
}).start;

// folded 3
Task({ ... }).start;

If ~pSeq is a Pseq, then this isn’t the correct usage. Patterns are blueprints for Streams, and you can pull values from Streams by calling .next on them - there is no random access into a stream using e.g. .wrapAt or similar. For simple uses, you can of course use normal arrays for this - you lose some of the composability and abstraction that comes from Streams, but normal array access makes sense to you and solves the problem I would suggest using it.

If you wanted to do this with patterns, here’s a few tips:

  1. Patterns must be turned into streams in order to pull values from them:
~pseq = Pseq(40 + [0, 2, 4], inf);
~midiStream = ~pseq.asStream;

~midiStream.next(); // inside your Task, to pull the next value from the stream
  1. You can return arrays from Streams, and index into those
~pseq = Pseq(40 + [[0, 3, 5], [0, 2, 4]], inf);
~midiStream = ~pseq.asStream;
~chord = ~midiStream.next(); // provides an array
~chord[index]
  1. You can nest patterns inside one another and consume values using .next as well. There are several ways to do this - here’s a few suggestions:
~pseq = Pseq([
   Pseq([0, 2, 4]),
   Pseq([0, 3, 5])
], inf);
~midiStream.nextN(6); // 0, 2, 4, 0, 3, 5

~pseq = Place([
   [0, 2, 4],
   [0, 3, 5]
], inf);
~midiStream.nextN(6); // 0, 0, 2, 3, 4, 5

~pseq = PpatLace([
   Pseq([0, 2, 4]),
   Pseq([0, 3, 5])
], inf);
~midiStream.nextN(6); // 0, 0, 2, 3, 4, 5
// same as above but the internal patterns can be things other than Pseq
  1. Pbind is an abstraction for doing all of these things together - you provide patterns or values for each synth parameter, and the Pbind produces a stream of events that can be played. Rewriting your originaly code (and assuming you’re feeding a Pattern to each of your parameters):
Pbind(
  \dur, 0.2,
  \midi, Pseq(40 + [0, 2, 4], inf),
  \tone, ~harmSeq,
  \art, ~durSeq,
  \amp, { rrand(0.1, 0.3) },   // wrap in a function so we generate a new value each time
  \pan, Pwhite(-1.0, 1.0)
).play

You can also do Pbind().asStream and simply pull events from the Pbind stream yourself inside, but there’s probably not a lot of value in doing it this way.

Hi Jordan,

Sorry I wrote that late last night and should have waited until today to write something more coherent.

Could you be a bit more descriptive?

I’m using arrays to control the playback of a sample. If the sample is 4 seconds long, and the array contains four integers e.g., [0, 1, 2, 3] then 0 will play the first second, 1 the second etc.

By reordering the array it reorders the sample playback.

e.g., [3, 2, 1, 0] would playback the audio slices in reverse order; the 4th second, the 3rd second… etc.

Here’s the current code slightly simplified:

~b0 = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
~b0.play;

m = [ [ 0, 1, 2, 3 ], [ 1, 0, 3, 2 ], [ 1, 3, 0, 2 ], [ 3, 1, 2, 0 ], [ 3, 2, 1, 0 ]; ]
m[1].postln;

d = ~b0.numFrames / ~b0.sampleRate; /// duration of sample
p = 4; /// number of parts to split the sample into
z = ~b0.numFrames / p; /// length of a slice in frames
y = z/~b0.sampleRate; /// length of a slice in seconds

(
SynthDef.new (\voice, {
	arg amp=1, out=0, gate=1, buf, start, end, rate=1, dur;
	var eg = EnvGen.kr(Env.asr(0.001, 1, 0.001), gate, doneAction: 2);
	var sig, ptr;
	ptr = Phasor.ar(0, BufRateScale.kr(buf)*rate, start, end);
	sig = BufRd.ar(2, buf, ptr);
	sig = sig * amp;
	Out.ar(out, (sig*eg));
}).add;
)


~b0.play;
p =   Pbind(           
	\instrument, \voice,
	\dur, y,
	\amp, 1,
	\start, Pseq(m[0]*z, 1).trace,
	\end, Pseq(m[0]*z+z, 1).trace,
).play;

It almost works, although there is bug, and the sample has significant gaps between the sections, so even though the array in the Pbind is [0, 1, 2, 3] it does not play back cleanly.

However this code does not actually achieve what I would like, it is meant as a stepping stone to greater complexity. What I want is to have the 0 in this part of the code increment every x number of repeats, thereby returning a new section of the array.

\start, Pseq(m[0]*z, 1).trace,
\end, Pseq(m[0]*z+z, 1).trace,

I’m aiming for a degree of flexibility in this because then it might also be interesting to have a pattern control the index of the array, so rather than increment, it could be random, or use a modulo, or whatever else.

To make matters more complicated I would like to have multiple instances of this running at the same time, to create layers. This is where the limits of my understanding of SC hit me sideways, because I have zero problem conceptualising it algorithmically, and huge problems with coding it.

So this is a rough summary:

There is an audio sample
There is an array of fixed length
The audio sample is played back in a fixed length loop but reordered depending on the array.
There is the capability of have multiple arrays of potentially different length, and for multiple voices to play simultaneously.

I don’t feel here, although I could be wrong, that my limitation is understanding the Patterns as such, which as a musician and composer I find quite intuitive and logical, it’s more the more fundamental grammar of SC, and building code of medium level complexity.

Hi Yann, thanks for the link. It’s interesting. I read through it. I think I understand everything there. I’ve also read the tutorials in the SC documentation, and watched Eli’s tutorial.

My sense - which could be wrong - is I’m struggling with some fundamental grammatical aspect of SC. I think I more or less ‘get’ the whole patterns concept. The simple examples make complete sense to me. My issue is adding just a bit more complexity and my coding grammar hits the wall.

Hi scztt,

Thanks for your reply. I have to work in 10 minutes, but will read your response in full. For greater context the code I took was from an example I wrote of from the SC book. Here is the full code for it:

( // first define the synth

SynthDef.new("SimpleBlip", {
arg midi = 60, tone = 10, art = 0.125, amp = 0.2, pan = -1;
var out, temper;
out =   Pan2.ar(
		    Blip.ar( // play the sequence
			    midi.midicps,
			    tone
		        ) * EnvGen.kr(Env.perc(0.01, art)),
		pan // pan left, center, or right
	);
DetectSilence.ar (out, doneAction:2);
amp = amp - ((midi - 60) * 0.02);
Out.ar(0, out*amp)
    }).add;
)

(
// Then run this task
(
~inst = [0, 0, 0];
~pSeq = [0, 0, 0];
~scaleAdd = [4, 5, 11, nil, 10, 3, 6, nil];
~notes =
[" C", " C#", " D", " Eb", " E", " F",
	" F#", " G", " Ab", " A", " Bb", " B"];
~rout = Task({
	inf.do({
		arg cnt1;
		var steps, durSeq, harmSeq;
		steps = rrand(6, 12);
		if(cnt1%6 == 0,
			{~scale = ~scale.add(~scaleAdd.wrapAt((cnt1/6).round(1) - 1));});
		"\nIteration: ".post; cnt1.post;
		[" (center) ", " (right) ", " (left) "].wrapAt(cnt1).postln;
		if(cnt1%24 == 0,
			{~scale = [0, 2, 7, 9];
				3.do({arg cnt2;
					~pSeq.wrapPut(cnt2,
						Array.fill(steps,
							{~scale.choose + [48, 60].choose}))})});
		"scale: ".post; ~scale.postln;
		~pSeq.wrapPut(cnt1,
			Array.fill(steps, {~scale.choose + [48, 60].choose}));
		"MIDI seq: ".post; (~pSeq.wrapAt(cnt1)%12).postln;
        "Sequence (notes): ".post;
         ~pSeq.wrapAt(cnt1).do(
         {arg thisItem; ~notes.at(thisItem%12).post});
         "".postln;
         harmSeq = Array.fill(steps, {rrand(1.0, 5.0)});
         durSeq = Array.fill(steps - 1, {rrand(0.01, 0.9)});
         ~inst.wrapAt(cnt1).stop;
         ~inst.wrapPut(cnt1,
               Task({
                   inf.do({arg cnt3;
                       Synth("SimpleBlip",
                           [\midi, ~pSeq.wrapAt(cnt1).wrapAt(cnt3),
                            \tone, harmSeq.wrapAt(cnt3),
                            \art, durSeq.wrapAt(cnt3),
                            \amp, rrand(0.1, 0.3),
                            \pan, cnt1.wrap(-1, 2)]);
                            0.125.wait;
                     })}).start;
               );
               12.wait;})
          }).start;
)
)

~rout.stop;
~inst.at(0).stop;
~inst.at(1).stop;
~inst.at(2).stop;



I do not know about the gaps because I do not use much Phasor.
I would do rather this way:

(
SynthDef.new (\voice, {
	arg amp=0.5, out=0, buf, start, dur;
	var sig, env;
	sig = PlayBuf.ar(1, buf, BufRateScale.ir(buf),startPos:start);
	env = EnvGen.kr(Env([0,1,1,0],[0.03,dur-0.06,0.03]),doneAction:2);
	sig = sig * amp;
	Out.ar(out, (sig*env));
}).add;
)

Maybe this is what you are looking for:

(
t = Task{
	m.size.do{|i|
		p = Pbind(           
			\instrument, \voice,
			\buf, ~b0,
			\dur, y,
			\amp, 1,
			\start, Pseq(m[i]*z, 1),			
		).play;
		~b0.duration.wait;
	}
}
)

t.start;
1 Like

Hi,

the miSCellaneous_lib quark contains a tutorial “Event patterns and array args”. Sorry, I have no time to dive into the concrete example now. The most common issue with patterns and array args though is that you’d have to pass arrays in double brackets (otherwise just multiple synths are generated). Hope that helps,

best

Daniel

This is great Yann, thank you.

So Task does seem the way to go with this. Your code is really concise. Also your SynthDef does eliminate the issue I had with the gaps.

Appreciate your effort, thanks.

Hi dkmayer,

Thanks for the tip and also the documentation. I’ve had a read of half of it today, and will dig further into it. Patterns really seem to be core to SC.