This perennial question made me realize that there are really THREE ways to do pattern-based composition, where your next event or section is triggered manually, e.g. by user interaction.
1. "I want instantaneous, zero-latency transitions: when I hit the button on my controller, I want my playing event to immediately end and the next one to start. I don’t care about note durs / deltas at all."
This case is addressed in the linked question, and some other places. If you want full manual control simply pull notes from your event stream and play them yourself:
~stream = Pdef(\notes).asStream;
~stream.next(()).play; // next event
~stream.next(()).play; // next event
~stream.next(()).play; // next event
One gotcha: you must have (\sendGate, false)
in your event, else Event:play
will automatically end the Event after \dur
beats.
2. "I want some Events to hold until I signal the pattern to move on, but I also want to use regular fixed-duration Events as well"
This requires a hacked / custom EventStreamPlayer. I want to note, very pragmatically, that this behavior sounds cool af but feels like it could be a nightmare to compose with and perform… It might be worth considering whether 1 or 3 are really more suitable.
3. "I want my pattern to wait for a signal it to move on, and I want it to advance in a musically meaningful way (e.g. on beat)
The idea here is that you want to keep your pattern running until you signal it, but you want the transition to the next pattern to happen e.g. on a beat interval, or after some sub-pattern finishes. This behavior is already implemented extensively by Pdef
and EventPatternProxy
. If you aren’t composing with “many Events” but really just want single monophonic-ish Event’s like #1 above, you can get this behavior easily by using Pmono
or PmonoArtic
.
This last case is more interesting to me, so I hacked around on an example to clarify how it can be done, and show off a few tricks that make it much more usable:
(
////////////////////////////////////////////////////////////////
// A synth for continuous playback
SynthDef(\monoSynth, {
var sig, env, lpf, res, detune, freq;
env = Env.adsr(0.1, 0.8, 0.2, 3).kr(gate:\gate.kr(1));
env = env + Env.perc(0.01, 0.3).kr(gate:Impulse.kr(2/3));
res = \res.kr(0.5).lag(0.25);
detune = \sawDetune.kr(0.05).lag(0.25);
lpf = \lpf.kr(1200);
freq = \freq.kr.lag(0.1);
lpf = env.linlin(
0, 1,
100, lpf
);
lpf = lpf.lag(0.01);
freq = freq * [0, detune].midiratio;
sig = LFSaw.ar(freq);
sig = RLPF.ar(sig, lpf, res);
sig = \amp.kr(1) * env * sig;
Out.ar(\out.kr(0), sig);
}).add;
////////////////////////////////////////////////////////////////
// A simple delay
SynthDef(\delay, {
var sig, feed;
feed = -6.dbamp * LocalIn.ar(2);
feed = LeakDC.ar(feed);
feed = DelayC.ar(feed, 4, [5.08/4, 6/4]);
feed = feed - FreqShift.ar(feed, SinOsc.kr([1, 1.1]).range(0, 0.15));
feed = Rotate2.ar(feed[0], feed[1], -0.1);
sig = In.ar(\in.kr, 2);
LocalOut.ar(
DelayN.ar(sig, 1, [4/4, 1/4]) + feed
);
sig = sig + feed;
Out.ar(\out.kr(0), sig);
}).add;
////////////////////////////////////////////////////////////////
// The most recently played event
~lastEvent = nil;
////////////////////////////////////////////////////////////////
// A base pattern for a mono synth (e.g. one event played continuously)
Pdef(\base, Pmono(
\monoSynth,
\dur, 1/8,
\octave, 4,
\callback, {
|event|
event[\isPlaying] = true; // bug in Pmono! this is never set.
~lastEvent = event // capture our lastEvent
}.inEnvir // this is executed with the Event as the envir, but we need ~lastEvent...
));
////////////////////////////////////////////////////////////////
// Four variations on the \base, with different notes and filter values
// Since \base is a Pmono, these only set values of the running \monoSynth.
Pdef(\a, Pbind(
\degree, [0, 3],
\lpf, 200
) <> Pdef(\base));
Pdef(\b, Pbind(
\lpf, Prand([800, 3200, 400], inf),
\degree, [-2, 2] + Prand([0, 0.2], inf),
) <> Pdef(\base));
Pdef(\c, Pbind(
\lpf, Prand([800, 5200, 700], inf),
\degree, [-3, 8] + Prand([0, 0.3], inf),
) <> Pdef(\base));
Pdef(\d, Pbind(
\lpf, Prand([800, 5200, 700], inf),
\degree, [1, 9] + Prand([-0.3, 0], inf),
) <> Pdef(\base));
////////////////////////////////////////////////////////////////
// Pdef(\section) will hold one of \a, \b, \c, \d - we'll swap them out
// when we want to change sections. Quant controls which beats we'll
// swap patterns on - so 2 means e.g. [108, 110, 112, ...]
Pdef(\section, nil).quant = 2;
////////////////////////////////////////////////////////////////
// Finally, we chain our section pattern with our delay via Pfx.
Pdef(\chain, Pfx(
Pdef(\section),
\delay
));
////////////////////////////////////////////////////////////////
// A simple routine to encapsulate stepping
// through each section \a...\d
~sequence = Routine({
// First, play the chain.
Pdef(\chain).play(quant:2);
loop {
[\a, \b, \c, \d].do {
|name|
// yield until we call next() on the routine to advance it again
"Playing %".format(name).postln;
Pdef(\section, Pdef(name)).yield;
}
}
});
)
With this setup, you have four sections (each of which is really just two monophonic notes), and you advance through them in your own sweet time using a Routine
.
You capture the most recent Event played by your sequence in ~lastEvent
, so you can set values on it manually (e.g. with code or with a controller). This is not the best way to control a running synth, but I think it illustrates something useful. You can control the above with code like:
// Start and advance the sequence.
~sequence.next();
// ~lastEvent can be used to set values on the synth also!
~lastEvent.set(\sawDetune, 0.4, \res, 0.6);
Some gotchas:
-
quant
controls when playback starts and when patterns are swapped in after a change. It’s more powerful than it looks - the documentation forPdef:quant
andQuant
have more details. - The overall granularity of WHEN your patterns swap out is still bound to the
\dur
of the events. If you specify an Event(dur: 4)
, playing it still “stops the world” for 4 beats. In general, you want\dur
's that are significantly smaller than the quant you’re using. -
However, note that
Pdef(\a)
has short\dur
s and is simply sending the same parameters to the already-running monophonic synth: I have no problems with granularity here, but I’ve still basically got one continuous & unchanging note playing. - If your music is not beat-gridded, and
quant
is not meaningful, keep in mind you can always have a quant of 0, and varying\dur
values. This ends up being a kind of quantization factor - for example, if you had:Pmono(\synth, \dur, Pseq([ 4, Pseq([ 1/8 ], inf) ])
, then the event will sustain a minimum of 4 beats (the first \dur is 4…), after which you’ll be able to transition with a granularity of 1/8 beats.