PR(\basicSynthChooser)
For each event, this process chooses one of any number of user defined synthdefs. It keeps a separate argument list for each synthdef, making it a very powerful process prototype.
Initialization
The process creates a MixerChannel for you, in the manner of bufPerc.
chan: Optional -- if you have a specific MixerChannel already created that you want the process to use for output, specify it here in the parameter dictionary. If you omit this parameter, the following parameters will be used to create a MixerChannel for you.
inChannels: The number of input channels for the MixerChannel.
outChannels: The number of output channels.
master: The target of this process' MixerChannel.
userprep: A function to create resources needed by a specific instance of this process.
userfree: A function to release resources created in userprep.
Required patterns
synth: A pattern that returns symbols identifying the synthdef to use for each event. By default, it chooses a key from the ~objects dictionary randomly.
delta: Number of beats until the next event.
sustain: For synthdefs with a gate argument, how long to hold the synth. (Synthdefs without a gate argument can use this parameter to decide how long to sustain.) The default pattern is Pkey(\delta), meaning that it will have the same value as delta.
Synthdef initialization
The ~objects dictionary defines synthdefs and arguments specific to each synthdef.
objects: (
synthdefID0: (
def: synthdefObject,
args: [
argName0: pattern0,
argName1: pattern1,
...
]
),
synthdefID1: (...)
)
synthdefID: A name (symbol) of your choosing, which will be used to identify the synthdef during playback.
def: An object that will be converted into a synthdef, same as in [defPerc]. The following objects are supported:
• "string" or \symbol: The name of a synthdef that you have already saved in the library using .store or .memStore.
• SynthDef(...): The synthdef will be .memStore'd in the library for you.
• Function { ... }: Will be converted into a synthdef and .memStore'd.
• Patch: Will be converted into a synthdef and .memStore'd. Do not use nested patches.
args: A list of arguments to calculate and send to the new synth node.
So you can change the argument patterns while the process is playing, each pattern is saved into the process environment and named after the synthdef ID plus the argument name. That is, if you have a synthdef with ID 'sinGrain' and an argument freq, the frequency pattern is saved into the environment variable sinGrainfreq. You can replace the pattern any time by executing the following:
BP(\synthChooser).sinGrainfreq = Pexprand(1000.0, 2500.0, inf);
For each event, only the arguments belonging to the selected synthdef are evaluated. If you need to share the same argument stream across multiple synthdefs (for example, to realize a single pitch contour but switch synthdefs on each note), use BPStream in the arguments arrays as a reference to this same environment variable. This technique appears in the example below.
Cloning (subclassing)
In addition to userprep and userfree, there are extra user hooks for initialization and release of resources, intended for use in subclasses. In this case, you might want to add initialization actions without destroying actions that were added in the ancestor process that you're cloning.
For example,
// PR(\basicSynthChooser) does basic initialization (MixerChannel creation, synthdef init).
PR(\basicSynthChooser).clone({
// Here, you want to create a variant that needs FFT buffers.
}) => PR(\fftSynthChooser);
PR(\fftSynthChooser).clone({
// Here, you need to add more resources
// but you also want to have the FFT buffers from PR(\fftSynthChooser)!
}) => PR(\fftSubclass);
In this situation, use the arrays ~userpreps and ~userfrees:
PR(\basicSynthChooser) does basic initialization (MixerChannel creation, synthdef init).
PR(\basicSynthChooser).clone({
// Here, you want to create a variant that needs FFT buffers.
~userpreps = ~userpreps.copy.add({ ... create FFT buffers here ... });
~userfrees = ~userfrees.copy.add({ ... remove FFT buffers here ... });
}) => PR(\fftSynthChooser);
PR(\fftSynthChooser).clone({
// Here, you need to add more resources
// but you also want to have the FFT buffers from PR(\fftSynthChooser)!
~userpreps = ~userpreps.copy.add({ ... create extra resources here ... });
~userfrees = ~userfrees.copy.add({ ... remove extra resources here ... });
}) => PR(\fftSubclass);
All the functions in the arrays will be executed at initialization and destruction. It's like calling super.prep or super.free (which, for number of reasons, you can't do directly in prototype-based programming).
Important: DO NOT hardcode a server into the ~server variable. When you chuck your custom PR into a BP, the server object will be copied and this causes lots of arcane, hard-to-troubleshoot problems. If you must give a non-default server in the PR, wrap it in a Function.
myOtherServer = Server(...);
PR(\basicSynthChooser).clone({
~server = { myOtherServer };
...
}) => PR(\mySynthChooser);
Example
// Distribute melody notes among multiple synthdefs.
s.boot;
TempoClock.default.tempo = 1;
(
PR(\basicSynthChooser).chuck(BP(\ex1), parms: (
inChannels: 2,
delta: 0.125,
freq: PdegreeToKey(Pvbrown(35, 50, Pexprand(1.0, 4.0, inf).trunc, inf),
#[0, 2, 4, 5, 7, 9, 11], 12).midicps,
pan: Pwhite(-1.0, 1.0, inf),
objects: (
sine: (
def: { |freq, amp, dur, pan|
var sig = SinOsc.ar(freq) * EnvGen.kr(Env.perc(0.01, dur), doneAction: 2);
Pan2.ar(sig, pan, amp)
},
args: [
freq: BPStream(\freq),
amp: Pwhite(0.3, 0.7, inf),
dur: Pif(Pwhite(0.0, 1.0, inf) <= 0.7, Pwhite(0.08, 0.11, inf), Pwhite(0.15, 0.25, inf)),
pan: BPStream(\pan)
]
),
saw: (
def: { |freq, amp, dur, pan, ffreq, rq|
var sig = Saw.ar(freq) * EnvGen.kr(Env.perc(0.01, dur), doneAction: 2);
Pan2.ar(RLPF.ar(sig, ffreq, rq), pan, amp)
},
args: [
freq: BPStream(\freq),
amp: Pwhite(0.2, 0.5, inf),
dur: Pwhite(0.15, 0.25, inf),
pan: BPStream(\pan),
ffreq: Pexprand(500.0, 2500.0, inf),
rq: Pwhite(4.0, 20.0, inf).reciprocal
]
),
pulse: (
def: { |freq, amp, dur, pan, ffreq, rq, width|
var sig = Pulse.ar(freq, width) * EnvGen.kr(Env.perc(0.01, dur),
doneAction: 2);
Pan2.ar(RLPF.ar(sig, ffreq, rq), pan, amp)
},
args: [
freq: BPStream(\freq),
amp: Pwhite(0.3, 0.7, inf),
dur: Pwhite(0.15, 0.25, inf),
pan: BPStream(\pan),
ffreq: Pexprand(500.0, 2500.0, inf),
rq: 0.9,
width: 0.5 // square wave only
]
)
)
));
)
BP(\ex1).play;
// change key
BP(\ex1).freq = PdegreeToKey(Pvbrown(35, 50, Pexprand(1.0, 4.0, inf).trunc, inf), #[0, 1, 3, 5, 7, 8, 10], 12).midicps;
// change width of pulse synthdef only
BP(\ex1).pulsewidth = Pwhite(0.15, 0.5, inf);
// also let's give the pulse a different filter contour
BP(\ex1).pulseffreq = Pexprand(2000.0, 5000.0, inf);
BP(\ex1).stop;
BP(\ex1).free;