Arrays, Routine, plotTree etc

Hi, i’m studying SC and i’ve some problems with a script i’m doing.
This is a project for an installation; the script works but there are some things i’d like to improve.

sample.wav is the sound of a plastic bag, sample2.wav is a voice that speaks.

  1. When you listen to the script, if you open plotTree you’ll notice, after the first turn of the array related to the step sequencer new objects are created. In the long run this couldweigh the laptop down until it crashes; initially i had thought of destroying synths when creating new ones, but it would mean stopping the audio files in progress. How can i make the array continuous, making the same voices sound and without creating new ones?

  2. Files always repeat the same frames, modified in pitch etc. but the point where the audio file is selected is the same. I tried to solve it by modifying the “pos” array variables but i don’t notice big differences.

  3. I tried to insert some slow envelopes to the single synths (applied to voce1, voce 2 etc.) but they don’t seem to start. It seems that the envelope starts with the beginning of the step sequencer array, regardless of whether the channels are playing or not.

  4. How can i insert a filter (a high shelf, for example) outside the SynthDef, in order to keep a single SynthDef for all the \sample and then insert a filter on only some of these?

My english is not great, i’m sorry.

(
Buffer.freeAll;
x = Buffer.read(s, "Sample.wav".resolveRelative);
y = Buffer.read(s, "Sample2.wav".resolveRelative);

b = Buffer.alloc(s, 2048);
b.sine3([1, 2, 3], [0.5,0.3,0.2], [2pi,2pi,2pi]); 

SynthDef(\sample, {arg buf    = 0,
                       freq   = 1,  
                       pos    = #[0, 0.5],  
                       tmp    = #[0, 1],          
                       amp    = #[0.5, 1],
                       trsp   = #[-1, 1],    
                       dir    = #[-1, 1],      
                       pan    = #[-1.0, 1],    
                       gate    = 0;    

                   var trig, dur, start, tsp, dire, sig, env, out, pann;

                       trig  = Impulse.kr(freq.lag(0.2));                     
	                   dur   = 1 / freq * (1 - TRand.kr(tmp[0], tmp[1], trig));         
                       start = TRand.kr(pos[0], pos[1], trig) * BufFrames.kr(buf);   
                       tsp   = TRand.kr(trsp[0], trsp[1], trig);         
                       dire  = TRand.kr(dir[0], dir[1], trig);            
                       sig   = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * tsp.midiratio * dire, trig, start);

                       env  = Select.kr(TRand.kr(0, 4, trig),
                                       [EnvGen.kr(Env.adsr(0.02, 0.3, dur - 0.5)),     
                                        EnvGen.kr(Env.triangle(dur), trig),          
                                        EnvGen.kr(Env.linen(0.01, 2, 0.5).duration_(dur), trig), 
			                            EnvGen.kr(Env.perc(0.01, 2).duration_(dur), trig),    
			                            EnvGen.kr(Env.sine(dur), trig)]);      

	                   env = EnvGen.kr(Env.adsr(10, 1, 1, dur - 9, 0.7)); 

                       out  = sig * env * TRand.kr(amp[0], amp[1], trig);
                       pann = Pan2.ar(out, TRand.kr(pan[0], pan[1], trig).varlag(1 / freq));

  	               Out.ar(0, pann)

                  }).add;

SynthDef(\pad, {arg buf    = b, 
	                freq   = 500, 
	                amp    = 0,   
	                dur    = 1,  
	                fade   = 0.1, 
	                t_gate = 0,  
	                pan    = 0,   
	                done   = 2;

	            var sig, bpf, env, pann;

	                sig  = Osc.ar(buf, freq);                           
	                bpf  = Env.linen(fade, dur - (fade * 2), fade, curve:\cub); 
	                env  = EnvGen.ar(bpf, t_gate, doneAction:done);
	                pann = Pan2.ar(sig * amp * env, pan);
	             Out.ar(0, pann)
         }).add;
)

(
~metro = TempoClock.new;

~voce1 = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
~voce2 = [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0];
~voce3 = [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0];
~voce4 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0];
~voce5 = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0];
~voce6 = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0];

~amp   = 10.collect({rand(1.0)}).normalizeSum;
~fase  = 10.collect({rand(2pi)});

p = Signal.sineFill(2048, 2, ~amp, ~fase);
b = Buffer.loadCollection(s, p.asWavetable);

Routine.new({
	         inf.do({arg item, id;

                     Synth(\sample, [\buf, x,
	                                 \freq, rrand(25, 26),
		                             \pos, [0.1, 0.2],
	                                 \tmp, [0.9, 1],
			                         \trsp, [rrand(20, 21), rrand(20, 21)],
	                                 \dir, [0, -1],
			                         \amp, [rrand(0.4, 0.5), rrand(0.4, 0.5)] * ~voce1.wrapAt(id),
			                         \pan, [-0.5, 0.5],
	                                 \gate, 1]);

		             Synth(\sample, [\buf, x,
	                                 \freq, rrand(1, 2),
	                                 \pos, [7, 8],
	                                 \tmp, [0.07, 0.08],
	                                 \trsp, [rrand(8, 9), rrand(8, 9)],
	                                 \dir, [-1, 1],
	                                 \amp, [rrand(0.9, 1), rrand(0.9, 1)] * ~voce2.wrapAt(id),
	                                 \pan, [-1, 1],
	                                 \gate, 1]);

		             Synth(\sample, [\buf, x,
	                                 \freq, rrand(1, 2),
	                                 \pos, [11, 12],
	                                 \tmp, [0.1, 0.2],
	                                 \trsp, [rrand(-7, -9), rrand(-7, -9)],
	                                 \dir, [0, -1],
		                             \amp, [rrand(0.7, 0.8), rrand(0.7, 0.8)] * ~voce3.wrapAt(id),
	                                 \pan, [-0.25, 0.25],
	                                 \gate, 1]);

                     Synth(\sample, [\buf, y,
	                                 \freq, rrand(12, 13),
	                                 \pos, [0.1, 0.2],
	                                 \tmp, [0.003, 0.004],
	                                 \trsp, [rrand(14, 15), rrand(15, 15)],
	                                 \dir, [-1, 1],
		                             \amp, [rrand(0.04, 0.05), rrand(0.04, 0.05)]* ~voce4.wrapAt(id),
	                                 \pan, [-0.5, 0.5],
	                                 \gate, 1]);

                     Synth(\sample, [\buf, y,
	                                 \freq, rrand(2, 3),
	                                 \pos, [7, 8],
	                                 \tmp, [0.007, 0.008],
	                                 \trsp, [rrand(8, 9), rrand(8, 9)],
	                                 \dir, [-1, 1],
		                             \amp, [rrand(0.9, 1), rrand(0.9, 1)] * ~voce5.wrapAt(id),
	                                 \pan, [-1, 1],
	                                 \gate, 1]);

                     Synth(\sample, [\buf, y,
	                                 \freq, rrand(1, 2),
	                                 \pos, [5, 12],
	                                 \tmp, [0.001, 0.02],
	                                 \trsp, [rrand(-2, -3), rrand(-2, -3)],
	                                 \dir, [-1, 1],
		                             \amp, [rrand(0.7, 0.8), rrand(0.7, 0.8)] * ~voce6.wrapAt(id),
	                                 \pan, [-0.25, 0.25],
	                                 \gate, 1]);

		             rrand(10, 15).wait; 
	                });
	        }).play(~metro);

Routine.new({
	          inf.do({Synth(\pad, [\buf, b,
			                       \freq, [50, 52, 53, 55].midicps.choose,
						           \amp, rand(0.5) * 0.3,
			                       \dur, rrand(4, 8),
			                       \fade, rrand(0.2, 8),
						           \pan, rand2(1.0),
			                       \done, 2,
			                       \t_gate, 1]);
		              rrand(0.1, 2).wait;
	                })
           }).play;
)

It isn’t necessary to release the old synths at the moment of creating new ones. You could have the synths release themselves when they are finished. Then you have the possibility of overlapping segments (a nice creative option) without build up over time.

Related:

env = EnvGen.kr(Env.adsr(10, 1, 1, dur - 9, 0.7));

ADSR always needs a gate. ADSR without a gate cannot release.

You have a gate argument – so, here would be the place to use it.

Since this is also the main volume envelope, here is also the place to release at the end.

env = EnvGen.kr(Env.adsr(10, 1, 1, dur - 9, 0.7), gate, doneAction: 2);

Now – if you make one other change – gate = 1; instead of 0 – now you can use events to trigger the synths. The default event type will automatically close the gate after an amount of time that you specify.

		(
			instrument: \sample,
			buf: x,
			freq: rrand(25, 26),
			pos: [[0.1, 0.2]],
			tmp: [[0.9, 1]],
			trsp: [[rrand(20, 21), rrand(20, 21)]],
			dir: [[0, -1]],
			amp: [[rrand(0.4, 0.5), rrand(0.4, 0.5)]] * ~voce1.wrapAt(id),
			pan: [[-0.5, 0.5]],
			sustain: /* here, choose how many beats to hold the synth open */
		).play;

And your other Synths could follow suit. (One detail is the double array – this is necessary in events for arrayed arguments.)

PlayBuf’s start position is given in sample frames, not seconds. So your distinction between 0.1 and 0.2, at 44.1 kHz, is about 0.002 ms.

Sorry, I misread the SynthDef. I usually calculate parameters in the language and I thought that’s what pos does. You do have start in frames – I don’t immediately see where this problem is, then.

Hm, I don’t see the code for this…?

In general I like the idea of modularity, but in this case, “insert a filter on only some of these” makes it a lot harder to modularize the filter.

If you really want to do it that way, then each time you want to filter, you would need to allocate a new stereo pair of buses (because the various filters should be isolated from each other – they can’t all run on the same bus) and release the bus numbers when the synths go away. This is possible, of course, but you would have to be careful with the bookkeeping. I would suggest to avoid that for now, unless you’re quite confident.

By contrast, if you have a no-filter SynthDef and a filtered SynthDef, then it’s easy to decide which SynthDef name to use at play time.

So there is one solution (external filter) that requires careful management of resources, and another solution (two SynthDefs) which is easy.

hjh

I tried to insert gate (which i had actually forgotten) and doneAction but nothing changes; i tried with t_gate and nothing is heard.

Then, i tried to start on the number of frames and nothing changes. The audio samples have about 7500000 frames, so i put numbers of that kind but the audio files always start from the same point.

                     Synth(\sample, [\buf, y,
	                                 \freq, rrand(2, 3),
	                                 \pos, [rand(500000), rand(600000)],
	                                 \tmp, [0.007, 0.008],
	                                 \trsp, [rrand(8, 9), rrand(8, 9)],
	                                 \dir, [-1, 1],
		                             \amp, [rrand(0.9, 1), rrand(0.9, 1)] * ~voce5.wrapAt(id),
	                                 \pan, [-1, 1],
						             \done, 2,
	                                 \gate, 1]);

Same result, why?

Anyway, the code for the main envelope could be this:

env = EnvGen.kr(Env.adsr(10, 1, 1, dur - 9, 0.7), gate, doneAction: 2);

Why did you put the arrays in additional square brackets?

Gated envelopes definitely work… If I have time later, I’ll rewrite it for you.

Again, there must be something else because your technique is valid. You’re generating a normalized random number (0.0 - 1.0) and then scaling it up by the total number of frames. I don’t see anything wrong there.

I’m puzzled; I’ve used PlayBuf’s startPos argument many many times and never had this problem.

Because if you have arrayed arguments and you’re using an event to play the synth, the arrayed arguments must be wrapped in a second array layer.

hjh

SynthDef like this:

SynthDef(\sample, {arg buf    = 0,
	freq   = 1,
	pos    = #[0, 0.5],
	tmp    = #[0, 1],
	amp    = #[0.5, 1],
	trsp   = #[-1, 1],
	dir    = #[-1, 1],
	pan    = #[-1.0, 1],
	gate    = 1;   // NOTE: DO NOT FORGET TO CHANGE THIS!

	var trig, dur, start, tsp, dire, sig, env, out, pann;

	trig  = Impulse.kr(freq.lag(0.2));
	dur   = 1 / freq * (1 - TRand.kr(tmp[0], tmp[1], trig));
	start = TRand.kr(pos[0], pos[1], trig) * BufFrames.kr(buf);
	tsp   = TRand.kr(trsp[0], trsp[1], trig);
	dire  = TRand.kr(dir[0], dir[1], trig);
	sig   = PlayBuf.ar(1, buf, BufRateScale.kr(buf) * tsp.midiratio * dire, trig, start);

	env  = Select.kr(TRand.kr(0, 4, trig),
		[EnvGen.kr(Env.adsr(0.02, 0.3, dur - 0.5)),
			EnvGen.kr(Env.triangle(dur), trig),
			EnvGen.kr(Env.linen(0.01, 2, 0.5).duration_(dur), trig),
			EnvGen.kr(Env.perc(0.01, 2).duration_(dur), trig),
			EnvGen.kr(Env.sine(dur), trig)]);

	env = EnvGen.kr(Env.adsr(10, 1, 1, dur - 9, 0.7), gate, doneAction: 2);

	out  = sig * env * TRand.kr(amp[0], amp[1], trig);
	pann = Pan2.ar(out, TRand.kr(pan[0], pan[1], trig).varlag(1 / freq));

	Out.ar(0, pann)

}).add;

For testing, I sped up the Routine (only because I don’t want to sit there for 20 seconds to see if it’s working):

~metro = TempoClock.new;

~voce1 = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

~amp   = 10.collect({rand(1.0)}).normalizeSum;
~fase  = 10.collect({rand(2pi)});

Routine.new({
	inf.do({arg item, id;

		(
			instrument: \sample,
			buf: x,
			freq: rrand(25, 26),
			pos: [[0.1, 0.2]],
			tmp: [[0.9, 1]],
			trsp: [[rrand(20, 21), rrand(20, 21)]],
			dir: [[0, -1]],
			amp: [[rrand(0.4, 0.5), rrand(0.4, 0.5)]] * ~voce1.wrapAt(id),
			pan: [[-0.5, 0.5]],
			sustain: rrand(2, 3)
		).play;
		
		rrand(1, 2).wait;
	});
}).play(~metro);

… and I see the number of synths fluctuating between 1, 2 and 3 – definitely not accumulating endlessly.

Gated envelopes are a pretty important node-control technique; best to get it under your belt.

I can confirm that PlayBuf is getting different start numbers (by tracing):

// run this and wait for a synth to play
o = OSCFunc({ |msg| s.sendMsg(\n_trace, msg[1]) }, '/n_go', s.addr).oneShot;

  unit 37 PlayBuf
    in  0 -0.325604 0 1.18001e+07 0 0

  unit 37 PlayBuf
    in  0 -1.65421 0 9.16662e+06 0 0

  unit 37 PlayBuf
    in  0 -3.21743 0 1.20868e+07 0 0

At least in the first Synth, you have freq = 25 or 26. That means 25 or 26 times per second, it will choose a new starting point. I don’t see how you can distinguish perceptually between two different random streams of starting points – at that speed, synth A and synth B will both just sound like a jumble. Then you say “they don’t sound different” but they are in fact not using the same starting coordinates.

If you want to differentiate starting points, the synth logic will have to change I guess? Not clear what you want.

hjh