Server control methods

A handful of filter patterns can isolate signals on a private bus and/or group, and also apply effect synths. A nice feature is that resources allocated at the beginning of the pattern are removed at the end. This is especially useful for effects, where you don't want to have a lot of effect synths left over taking up CPU but not processing audio.

- Pbus(pattern, dur, fadeTime, numChannels, rate): Creates a private group and bus for the synths played by the pattern. The group and bus are released when the pattern stops. Useful for isolating signals from different patterns.
- Pgroup(pattern): Creates a private group (without private bus) for the pattern's synths.

- Pfx(pattern, fxname, pairs)
- Pfxb(pattern, fxname, pairs): Both of these patterns play an effect synth at the tail of the target group. This synth should read from the bus identified by the 'out' argument, and write the processed signal onto the same bus using either ReplaceOut or XOut. Pfx uses whatever bus and group are specified in the incoming event. Pfxb allocates a separate bus and group for the effect and the pattern.

There are a lot of permutations when it comes to signal routing and effect management, too many to discuss in depth here. Some of the main scenarios are:

- Separate effects that should apply individually: the patterns and effects should be isolated on separate buses. Pfxb handles this isolation automatically: two patterns like Pfxb(Pbind(...), \fxname, \effectargName, value, \name, value...) will play on separate buses and their signals will not interfere with each other.

- Effects that should apply as a chain: both effects should use the same bus, and the effect patterns should be nested to string them together. The outermost effect should use Pfxb to allocate a separate group and bus for this signal chain; inner ones should use Pfx to piggyback on the existing bus.

Pfxb(

Pfx(

(event pattern here),

\synthDefNameOfFirstEffectInChain,

(argument list for the first effect),

)

\synthDefNameOfSecondEffectInChain,

(argument list for the second effect)

).play;

More complex arrangements are possible through nesting, and parallelizing Pfx or Pfxb patterns using Ppar and its cousins.

This example uses Pfxb to isolate a pair of separately-sounding patterns on different buses, and to pass the two signals' streams through separate volume controls. The effect synth, for volume, is kept deliberately simple for the example, but of course it can do any kind of signal processing you like.

It might seem odd at first to use a gated envelope for an effect, but this is important to keep the signal's integrity. If the gate is not there, the effect synth will be n_free'd (brutally cut off), probably before the nodes played by the source pattern have finished. In this case it would produce a sudden, brief jump in volume at the end. The gate, combined with the one-second release in the envelope, keeps the effect synth around long enough to allow its source synths to become silent first.

Remember that streams made from patterns don't expose their internals. That means you can't adjust the parameters of an effect synth directly, because you have no way to find out what its node ID is. The example addresses this problem by allocating a couple of control buses for the amplitude values, and mapping the volume synths to those control buses. Then the little GUI needs only to update the control bus values.

// Demonstrates how Pfxb isolates signals on different buses

// The fx synth is a simple volume control here

// but it could be more complex

(

SynthDef(\volumeCtl, { |out, amp = 1, gate = 1|

var sig = In.ar(out, 2) * amp;

sig = sig * EnvGen.kr(Env(#[1, 1, 0], #[1, 1], -3, releaseNode: 1), gate, doneAction: 2);

ReplaceOut.ar(out, sig)

}).add;

~vbus1 = Bus.control(s, 1).set(0.5);

~vbus2 = Bus.control(s, 1).set(0.5);

~window = Window.new("mixers", Rect(10, 100, 320, 60));

~window.view.decorator = FlowLayout(~window.view.bounds, 2@2);

EZSlider(~window, 310@20, "low part", \amp, { |ez| ~vbus1.set(ez.value) }, 0.5);

~window.view.decorator.nextLine;

EZSlider(~window, 310@20, "high part", \amp, { |ez| ~vbus2.set(ez.value) }, 0.5);

~window.front.onClose_({ ~vbus1.free; ~vbus2.free });

)

(

p = Ppar([

Pfxb(Pbind(

\degree, Pseq([0, 7, 4, 3, 9, 5, 1, 4], inf),

\octave, 4,

\dur, 0.5

), \volumeCtl, \amp, ~vbus1.asMap), // map to control bus here

Pfxb(Pbind(

\degree, Pwhite(0, 11, inf),

\dur, 0.25

), \volumeCtl, \amp, ~vbus2.asMap) // ... and here

]).play;

)

p.stop;

Third-party extension alert: Pfx and its cousins work on the philosophy that a signal routing arrangement should be created as needed (when its subpattern is playing) and removed immediately when the pattern is finished. Another approach is to treat signal routing and effects as a persistent infrastructure, created and destroyed under the user's control (not the pattern's). JITLib's proxy system offers some support for this. MixerChannels (in the ddwMixerChannel quark) are a more explicit way. Any pattern can be played on a MixerChannel: aMixer.play(aPattern).

Pproto: Allocating other resources for the duration of a pattern

It's also possible to load sound file or wavetable buffers or play synths as part of the preparation to run a Pbind-style pattern. When the Pbind stops, those resources would be removed automatically from the server.

The mechanism to do this is a bit unlike most of the other protocols to use the server in SuperCollider. To create the resources, Pproto takes a function in which one or more Event objects contain the instructions to create them. These events should use specific event types, described in Pproto's help file. The pattern is able to clean up the resources because each event has an associated cleanup action (see the event types with cleanup class). Thus, Pproto needs only to remember the events representing the resources, and execute their cleanup actions at the end.

The Pproto help file has several complex examples that are worth reading. Here is just one simple case that loads the standard a11wlk01.wav sound file and plays fragments from it.

(

SynthDef(\playbuf, { |bufnum, start, dur = 1, amp = 0.2, out|

var sig = PlayBuf.ar(1, bufnum, BufRateScale.ir(bufnum), 0, start);

sig = sig * amp * EnvGen.kr(Env.linen(0.01, dur, 0.01), doneAction: 2);

Out.ar(out, sig ! 2)

}).add;

)

(

TempoClock.default.tempo = 1;

p = Pproto({

~buf = (type: \allocRead, path: "sounds/a11wlk01.wav").yield;

}, Pbind(

\instrument, \playbuf,

// access resources in the protoevent by Pkey

\bufnum, Pkey(\buf),

\dur, Pwhite(1, 4, inf) * 0.25,

// upper bound of Pwhite is based on duration

// so that start + (dur * samplerate) never goes past the buffer's end

\start, Pwhite(0, 188893 - (Pkey(\dur) * 44100), inf)

)).play;

)

// shows a buffer number allocated ('true' ContiguousBlock)

s.bufferAllocator.debug;

p.stop;

s.bufferAllocator.debug; // after stop, the buffer is gone

Previous: PG_06e_Language_Control

Next: PG_06g_Data_Sharing