A very straightforward way of making a modular-ish setup is to use proxy’s plus mapping of audio parameters. This allows you to specify routing separately from where you define your synths, making it easy to re-configure a small number of modules in a wide variety of ways.
Here’s a quickie example - I’ll use the Ndef
interface for proxys rather than ~environmentVariables
and a proxyspace (Ndefs are just NodeProxys) - I prefer this, and it has some benefits, but if you’re working in a proxyspace you can swap any Ndef(\foo, { ... })
to ~foo = { ... }
. And, I’m using the NamedControl way of specifying synth params, where { SinOsc.ar(\freq.kr) }
is equivalent to { |freq| SinOsc.ar(freq) }
. I’m doing this because I can do e.g. \input.ar
, where the .ar
specifies an audio input.
Ndef(\click, {
Impulse.kr(1);
});
Ndef(\envelope, {
Env.adsr().kr(gate: \trigger.kr);
});
Ndef(\osc1, {
LFSaw.ar(\freq.kr, 0, \amp.kr);
});
Ndef(\osc2, {
SinOsc.kr(\freq.kr).range(\min.kr, \max.kr);
});
Ndef(\filter, {
BLowPass.ar(\in.ar, \freq.kr(400));
});
Okay, now that I have my modules… I can map outputs to inputs using a.set(b.asMap)
:
Ndef(\envelope).set(
\trigger, Ndef(\click).asMap
);
Ndef(\osc1).set(
\freq, 140,
\amp, Ndef(\envelope).asMap
);
Ndef(\osc2).set(
\freq, 1/8,
\min, 100, \max, 600
);
Ndef(\filter).set(
\in, Ndef(\osc1).asMap,
\freq, Ndef(\osc2).asMap
);
Ndef(\filter).play;
You can easily switch between arguments with a fixed value (e.g. my \freq, 140
) and dynamic inputs (\freq, Ndef(\osc2).asMap
). The only gotcha with all of this is ---- you MUST map kr
outputs to kr
inputs and ar
with ar
, else you probably won’t get any output (note that my \osc2
is a SinOsc.kr
, and I’m mapping it to a kr
input of my filter). You can easily sidestep this by simply sticking to ar
values for everything - it will come at a higher CPU cost, but you can always go back and switch some things back to kr
if you build something complex enough that efficiency becomes important.
Also be aware of the wonderful xset
variation, which will smoothly crossfade between one mapping any another based on the fadeTime
set for each synth - this means you can smoothly crossfade between completely different routing setups without interrupting playback.