Cannot sequence Synths in Pbinds with Pseq, can sequence Synths in Tdef

I’m a little confused what i am doing wrong here. I have two synths, one will play (very short parts of) samples, the other is a temporary synth (here just playing sine tones to test sequencing). no matter what i do only the sample player plays. am i using pseq incorrectly here? I can get what I want working with a Tdef (at the bottom) but I would like to be able to algorithmically generate phrases of x number of synths and doing this with Patterns. Below are the synths, further down the not working Pseq version, and below that the Tdef version. What am I doing wrong with Patterns here?

P.S. the busses (for amplitude and duration of synth playback) are in a different file and left out here.

//Same for both
s.boot;

~monologue = Buffer.read(s, "FILEPATH" );

(
SynthDef(\slicePlayer, { |ampControl=0, gate=1, out=0, bufnum=0, start=0, len=0.3|
    var sig, env, amp;
    amp = In.kr(ampControl, 1);
    env = EnvGen.kr(Env.perc(0.005, len*0.9), gate, doneAction:2);
    sig = PlayBuf.ar(1, bufnum, 1.0, startPos:start, loop:0);
    Out.ar(out, sig * amp * env);
}).add;

SynthDef(\sineTest, { |ampControl=0, gate=1, out=0, dur=0.2, freq=440|
    var sig, env, amp;
    amp = In.kr(ampControl, 1);
    env = EnvGen.kr(Env.perc(0.005, dur*0.9), gate, doneAction:2);
    sig = SinOsc.ar(freq) * amp * env;
    Out.ar(out, sig);
}).add;
);

Patterns (not working, only first synth plays)

(
Pdef(\gesture, Pseq([
    // Slice
    Pbind(
        \instrument, \slicePlayer,
        \bufnum, ~monologue,
        \ampControl, ~twisterBusses[0],
        \maxLenBus, ~twisterBusses[4],
        \len, Pfunc({ |ev|
            var knob = ev[\maxLenBus].getSynchronous ?? 0.5;
            rrand(0.22, 0.8 + (knob * 5.0));
        }),
        \delta, Pfunc({ |ev| ev[\len] + 0.18 }),
        \start, Pfunc({ |ev|
            var safe = ~monologue.numFrames - (ev[\len] * ~monologue.sampleRate * 1.2);
            rrand(0, safe.asInteger.max(0));
        })
    ),

    // Sine
    Pbind(
        \instrument, \sineTest,
        \ampControl, ~twisterBusses[1],
        \freq, Pwhite(120, 1400, inf),
        \dur, Pwhite(0.25, 0.9, inf),
        \delta, Pkey(\dur) + 0.25
    )
], inf)
);
)

Pdef(\gesture).play;
Pdef(\gesture).stop;

Tdef

(
Tdef(\gesture, {
    var sliceDur, sineDur;

    loop {
        sliceDur = rrand(0.22, 0.8 + (~twisterBusses[4].getSynchronous ?? 0.5 * 5.0));
        sineDur  = rrand(0.2,  0.6 + (~twisterBusses[5].getSynchronous ?? 0.5 * 4.0));

        // Slice
        Synth(\slicePlayer, [
            \ampControl, ~twisterBusses[0],
            \bufnum, ~monologue,
            \start, rrand(0, (~monologue.numFrames * 0.8).asInteger),
            \len, sliceDur,
            \gate, 1
        ]);

        sliceDur.wait;

        // Sine
        Synth(\sineTest, [
            \ampControl, ~twisterBusses[1],
            \freq, rrand(120, 1400),
            \dur, sineDur,
            \gate, 1
        ]);

        sineDur.wait;
    }
}).play;
)

// Stop
Tdef(\gesture).stop;

Pbind makes as many events as it can from the given patterns inside it. If all of the member patterns are infinite in length, then the Pbind is infinite in length.

So your Pseq means, first play this infinite series of sample-based notes; then when that infinite series ends, continue on with the other synth.

But the infinite series will never end.

So you need something in there to stop the first Pbind.

One easy way is Pfin(numberOfEvents, Pbind(...)).

Or, put a limit on the number of events of one of the member streams, e.g. \instrument, Pn(\slicePlayer, 1) for a single output sample (and you can change the number as you like).

hjh

Hi James,

Thanks for this suggestion. I have refactored it like so

(
Pdef(\gesture, Pseq([
    Pfin(1, Pbind(
        \instrument, \slicePlayer,
        \bufnum, ~monologue,
        \ampControl, ~twisterBusses[0],
        \maxLenBus, ~twisterBusses[4],

        \len, Pfunc({ |ev|
            var knob = ev[\maxLenBus].getSynchronous ?? 0.5;
            rrand(0.22, 0.8 + (knob * 5.0));
        }),
        \delta, Pfunc({ |ev| ev[\len] }),
        \start, Pfunc({ |ev|
            var safe = ~monologue.numFrames - (ev[\len] * ~monologue.sampleRate * 1.2);
            rrand(0, safe.asInteger.max(0));
        })
    )),

    // One Sine - finite
    Pfin(1, Pbind(
        \instrument, \sineTest,
        \ampControl, ~twisterBusses[1],
        \freq, Pwhite(120, 1000),
        \dur, Pwhite(0.1, 0.5),
        \delta, Pkey(\dur)
    ))
], inf)
);
)

Pdef(\gesture).play;
Pdef(\gesture).stop;

Another quick question related to this: i want this to be really fast/tight and currently there’s lots of pauses. I can of course use an Env with less of a fade time but I am presently getting a lot of FAILURE IN SERVER /n_set Node 2064 not found messages in the post because, presumably, the envelope has already released. If I want this to be really tight from a timing standpoint would it be better to take a different approach? Maybe a Tdef? Or using Phasor to scrub through a buffer? Open to any suggestions you may have, the goal would be to have a way to algorithmically generate phrases so figured Patterns would be best but struggling with some of the details.

Took a stab at rewriting the SynthDef to use Phasor with BufRd in a Tdef, seems tighter? Also controlling number of repeats via Midi Controller here.

(
SynthDef(\bufSlice, { |ampControl=0, gate=1, out=0, bufnum=0, start=0, len=0.3|
    var sig, env, amp, phasor;
    amp = In.kr(ampControl, 1);
    phasor = Phasor.ar(gate, BufRateScale.kr(bufnum), start,
		start + (len * BufSampleRate.kr(bufnum)), start);
    sig = BufRd.ar(1, bufnum, phasor, 0, 4);
    env = EnvGen.kr(Env.perc(0.005, len * 0.9), gate, doneAction:2);
    sig = sig * amp * env;
    Out.ar(out, sig);
}).add;

SynthDef(\sineTest, { |ampControl=0, gate=1, out=0, dur=0.2, freq=440|
    var sig, env, amp;
    amp = In.kr(ampControl, 1);
    env = EnvGen.kr(Env.perc(0.005, dur*0.9), gate, doneAction:2);
    sig = SinOsc.ar(freq) * amp * env;
    Out.ar(out, sig);
}).add;
);

(
Tdef(\gesture, {
    loop {
        var sliceRepeats = (~twisterBusses[8].getSynchronous ?? 0.0).linlin(0.0, 1.0, 1, 5).asInteger.max(1);
        var sineRepeats  = (~twisterBusses[9].getSynchronous ?? 0.0).linlin(0.0, 1.0, 1, 5).asInteger.max(1);

        sliceRepeats.do {
            var len = rrand(0.12, 0.6 + ((~twisterBusses[4].getSynchronous ?? 0.5) * 4.0));
            var startPos = rrand(0, (~monologue.numFrames - (len * ~monologue.sampleRate * 1.15)).max(0).asInteger);

            Synth(\bufSlice, [
                \ampControl, ~twisterBusses[0],
                \start, startPos,
                \len, len,
                \bufnum, ~monologue
            ]);
            len.wait;
        };

        sineRepeats.do {
            var dur = rrand(0.2, 0.8 + ((~twisterBusses[5].getSynchronous ?? 0.5) * 4.0));
            Synth(\sineTest, [
                \ampControl, ~twisterBusses[1],
                \freq, rrand(120, 1400),
                \dur, dur
            ]);
            dur.wait;
        };
    }
});
)

Tdef(\gesture).play;
Tdef(\gesture).stop;

This is because you’re giving conflicting information to the pattern system: the presence of the gate argument tells the event that it should explicitly release the note, but the envelope (.perc) runs on its own timing and ignores the gate.

When using a timed envelope, it’s better if the SynthDef doesn’t have a gate. A gate should be reserved for envelopes like Env.adsr or Env.asr.

hjh

1 Like

Oh wow, good catch, I was so focused on other things I missed that. I have updated the SynthDefs accordingly.

So if I understand properly I can dynamically change number of repeats per synth with Pfin but in the below example Pseq regularly gets stuck when I try to set Pfin number of repeats with a MIDI Controller (talking to a control bus at ~twisterBusses[9] in this case). Is there a way to control number of repeats this way without breaking Pseq? FWIW I am getting the behavior I am looking for with Tdef, which I have included below my specific Pfin code below.

(
Pdef(\gesture, Pseq([
	// 2 slice test (hard code repeats work fine)
    Pfin(2, Pbind(
        \instrument, \bufSlice,
        \ampControl, ~twisterBusses[0],
        \maxLenBus, ~twisterBusses[4],
        
        \len, Pfunc({ |ev|
            var dur = rrand(0.12, 0.6 + ((ev[\maxLenBus].getSynchronous ?? 0.5) * 4.0));
            postf("→ slice | base = %s\n", dur.round(0.003));
            dur
        }),
        
        \start, Pfunc({ |ev|
            var len = ev[\len];
            var safe = ~monologue.numFrames - (len * ~monologue.sampleRate * 1.15);
            rrand(0, safe.asInteger.max(0));
        }),
        
        \dur, Pkey(\len),
        \delta, Pkey(\len),
        \bufnum, ~monologue
    )),

	// Dynamically set repeats dont work, here is where it gets stuck forever
    Pfin(
        Pfunc({
            var reps = (~twisterBusses[9].getSynchronous ?? 0.0)
                .linlin(0.0, 1.0, 1, 5).asInteger.max(1);
            postf("→ SINE: % repeats (knob 9 = %)\n", reps, (~twisterBusses[9].getSynchronous ?? 0).round(0.001));
            reps
        }),
        Pbind(
            \instrument, \sineTest,
            \ampControl, ~twisterBusses[1],
            
            \dur, Pfunc({ |ev|
                var dur = rrand(0.2, 0.8 + ((~twisterBusses[5].getSynchronous ?? 0.5) * 4.0));
                postf("   → sine  | base = %s\n", dur.round(0.003));
                dur
            }),
            
            \freq, Pwhite(120, 1400),
            \delta, Pkey(\dur)
        )
    )
], inf));
)

versus with Tdef (using Dictionaries with this one, SynthDefs are identical)

~voices = Dictionary.new;

(
~voices[\slice] = (
    synth: \bufSlice,
    ampBus: 0,
    durBus: 4,
    repeatBus: 8,
    makeParams: { |v|
        var len = rrand(0.12, 0.6 + ((~twisterBusses[v.durBus].getSynchronous ?? 0.5) * 4.0));
        var startPos = rrand(0, (~monologue.numFrames - (len * ~monologue.sampleRate * 1.15)).max(0).asInteger);
        [\ampControl, ~twisterBusses[v.ampBus], \start, startPos, \len, len, \bufnum, ~monologue]
    },
    getDur: { |v|
        var len = rrand(0.12, 0.6 + ((~twisterBusses[v.durBus].getSynchronous ?? 0.5) * 4.0));
        len
    }
);

~voices[\sine] = (
    synth: \sineTest,
    ampBus: 1,
    durBus: 5,
    repeatBus: 9,
    makeParams: { |v|
        var dur = rrand(0.2, 0.8 + ((~twisterBusses[v.durBus].getSynchronous ?? 0.5) * 4.0));
        [\ampControl, ~twisterBusses[v.ampBus], \freq, rrand(120, 1400), \dur, dur]
    },
    getDur: { |v|
        var dur = rrand(0.2, 0.8 + ((~twisterBusses[v.durBus].getSynchronous ?? 0.5) * 4.0));
        dur  // ← Direct calculation
    }
);
)

(
// ==================== Gesture Generator ====================
Tdef(\gesture, {
    loop {
        var numVoices = rrand(1, 3);
        var active = ~voices.keys.asArray.scramble[0..(numVoices-1)];
        
        active.do { |key|
            var voice = ~voices[key];
            var repeats = (~twisterBusses[voice.repeatBus].getSynchronous ?? 0.0)
                .linlin(0.0, 1.0, 1, 5).asInteger.max(1);
            
            repeats.do {
                var params = voice.makeParams.value(voice);
                var baseDur = voice.getDur.value(voice);
                var variedDur = baseDur * rrand(0.65, 1.55);
                
                Synth(voice[\synth], params);
                
                postf("   → % | base = %s | varied = %s\n", 
                    key, baseDur.round(0.003), variedDur.round(0.003));
                
                variedDur.wait;
            };
        };
        
		// rrand(0.15, 0.85).wait;
    }
});
)


Tdef(\gesture).play;
Tdef(\gesture).stop;

Okay, I think this is the fix (props to hemiketal on the SC discord for the help with this): apparently Pfin only accepts certain things as inputs so this had to be rewritten with a function as input directly. Looks like this:

(
Pdef(\gesture, Pseq([

    Pfin(
        {
            var reps = (~twisterBusses[8].getSynchronous ?? 0.0)
                .linlin(0.0, 1.0, 1, 5).asInteger.max(1);
            postf("→ SLICE: % repeats (knob 8 = %)\n", reps, (~twisterBusses[8].getSynchronous ?? 0).round(0.001));
            reps
        },
        Pbind(
            \instrument, \bufSlice,
            \ampControl, ~twisterBusses[0],
            \maxLenBus, ~twisterBusses[4],
            \len, Pfunc({ |ev|
                var dur = rrand(0.12, 0.6 + ((ev[\maxLenBus].getSynchronous ?? 0.5) * 4.0));
                postf("→ slice | base = %s\n", dur.round(0.003));
                dur
            }),
            \start, Pfunc({ |ev|
                var len = ev[\len];
                var safe = ~monologue.numFrames - (len * ~monologue.sampleRate * 1.15);
                rrand(0, safe.asInteger.max(0));
            }),
            \dur, Pkey(\len),
            \delta, Pkey(\len),
            \bufnum, ~monologue
        )
    ),

    Pfin(
        {
            var reps = (~twisterBusses[9].getSynchronous ?? 0.0)
                .linlin(0.0, 1.0, 1, 5).asInteger.max(1);
            postf("→ SINE: % repeats (knob 9 = %)\n", reps, (~twisterBusses[9].getSynchronous ?? 0).round(0.001));
            reps
        },
        Pbind(
            \instrument, \sineTest,
            \ampControl, ~twisterBusses[1],
            \dur, Pfunc({ |ev|
                var dur = rrand(0.2, 0.8 + ((~twisterBusses[5].getSynchronous ?? 0.5) * 4.0));
                postf(" → sine | base = %s\n", dur.round(0.003));
                dur
            }),
            \freq, Pwhite(120, 1400),
            \delta, Pkey(\dur)
        )
    )

], inf));
)

Pdef(\gesture).play;
Pdef(\gesture).stop;

It’s a general thing, and a definite point of confusion in the pattern system. Some pattern inputs make sense to update for every output value, while others (such as a number of output values, or starting value of a series) should be calculated once at the start of the pattern and held until the pattern releases control. The second type can’t be a pattern because it needs to keep its state across multiple times through, but it can be a function or a stream (Pn(Pseries(Pseries(0, 2, inf).asStream, 2, 3), 6) for instance = sliding arpeggiation: 0 2 4 2 4 6 4 6 8 etc).

The catch is that there’s nothing in SC syntax to distinguish these. You just have to “know,” which of course you can’t see just from reading examples.

But after more exposure, you’ll remember more of them.

hjh

1 Like