Patch
a Player
An Instr (Instrument) is a function that has a unique name attached to it.
A Patch specifies an Instr by name and specifies inputs that will be passed to the Instr function when it is actually played. A Patch is the player that actually plays an Instr's sound function.
Patch(instr,args)
instr
an instr name
a function
an Instr object
args - an array of
floats
players of any type
many other types of objects
It all depends what the instr function accepts.
(
// given a simple instrument
Instr(\Pulse,{ arg freq=440.0,width=0.5,mul=1.0;
Pulse.ar( freq, width, mul )
});
// a patch specifies inputs (arguments) to that instrument
p = Patch( \Pulse, [
300,
0.2,
0.1
]);
//the default server will be booted, the SynthDef written and loaded
p.play;
)
p.stop;
// command-. will also stop all sounds
Instr can be specified as
by the Instr name (see Instr)
// the Instr stores itself when created
(
// this can be kept in a separate file
Instr(\bubbles, { arg amp=1.0;
var f, zout;
f = LFSaw.kr(0.4, 0, 24, LFSaw.kr([8,7.23], 0, 3, 80)).midicps;
zout = CombN.ar(SinOsc.ar(f, 0, 0.04), 0.2, 0.2, 4);
zout * amp
});
// the patch retrieves it
p = Patch(\bubbles,[0.4] );
p.gui
)
a Function
(
p = Patch({ arg freq=100,amp=1.0;
SinOsc.ar([freq,freq + 30],0,amp)
},[
500,
0.3
]);
p.gui
)
or directly as an Instr object
(
i = Instr("help-Patch",{ arg freq=100,amp=1.0;
SinOsc.ar([freq,freq + 30],0,amp)
});
p = Patch(i,[ 500, 0.3 ]);
p.gui
)
The args
The args may be whatever the instr function is willing to accept.
These may be simple floats,
Envelopes,
Audio rate Players
Control rate Players
(
Instr(\bubbles, { arg amp=1.0;
var f, zout;
f = LFSaw.kr(0.4, 0, 24, LFSaw.kr([8,7.23], 0, 3, 80)).midicps;
zout = CombN.ar(SinOsc.ar(f, 0, 0.04), 0.2, 0.2, 4);
zout * amp
});
Instr(\rlpf,{ arg audio=0,freq=500,rq=0.1;
RLPF.ar(audio, freq, rq)
});
p = Patch(\rlpf,[
q = Patch(\bubbles)
]);
p.gui
)
Each argument has a Spec. See Instr for how these specs are determined.
An argument may be nil. For a nil argument, Patch will ask the spec to create a suitable defaultControl.
ControlSpec : KrNumberEditor
StaticSpec : NumberEditor (for quantities or max delay times etc.)
a static spec is a non-modulateable control
EnvSpec : EnvEditor
SampleSpec : Sample
The instrument may specify default values in the arg names of its function:
(
Instr(\bubbles, { arg speed = 0.4, amp=0.4;
var f, zout;
f = LFSaw.kr(speed, 0, 24, LFSaw.kr([8,7.23], 0, 3, 80)).midicps;
zout = CombN.ar(SinOsc.ar(f, 0, 0.04), 0.2, 0.2, 4);
zout * amp
});
// the patch specifies the actual values to be used
p = Patch(\bubbles,
[
0.01 // speed is a fixed float of 0.01
// but amp is not specified, so the default 0.4 from the Instr function is used
]);
// but notice that it is a KrNumberEditor that was created, and it inits to the default 0.4
// from the instr function
p.gui;
)
If you wanted to build your patch with a KrNumberEditor (a slider) defaulted to 0.01 for the speed:
(
Patch(\bubbles,
[
KrNumberEditor(0.01,\speed)
]).gui
)
Automatic Input Creation
For any nil arguments, a default control will be created.
This gives the impression that Patch is "an automatic gui for an Instr / SynthDef".
If you do not supply arguments, it will make default ones, simple ones, but the real power of Patch is to supply functions with complex and varied inputs. Sitting there with 5 sliders on a 1 dimensional Instrument isn't really the height of synthesis.
I recommend experimenting with factory methods to create your patches, supplying them with inputs useful for what you are working on.
eg. If you use a certain physical controller or wacom :
buildPatch = { arg instrName;
var i;
i = Instr.at(instrName);
Patch(instrName,[
{ i.specAt(0).map( JoyAxis.kr(0,1,axis:5) ) },
{ i.specAt(1).map( JoyAxis.kr(0,1,axis:5) ) },
])
};
// this creates a Patch
buildPatch.value( \boingboing );
You could even interrogate the instr to see which inputs might make good candidates for your JoyAxis.
Remember, Instr are not just for audio functions, so you can even keep your factories themselves in Instrument libraries:
Instr(\joysticker,[ arg instrName;
var i;
i = Instr.at(instrName);
Patch(instrName,[
{ i.specAt(0).map( JoyAxis.kr(0,1,axis:5) ) },
{ i.specAt(1).map( JoyAxis.kr(0,1,axis:5) ) },
])
});
patch = Instr(\joysticker).value( \simple );
You have just used an Instr function to create and return a Patch.
This Instr is not used for audio, its just used to build and return a Patch
You could choose different controllers for different common inputs,
you can query the argument name and the spec.
Keep files in databases, load other Patches or soundfiles from disk.
You could flip coins and choose from soundfiles, audio in, other saved
patches or randomly chosen net radio feeds.
Fixed Arguments
Floats and other scalar values including Envelopes, are transparently dealt with by Patch. These items are passed to the Instr function, but not to the SynthDef or the Synth. They are not modulateable.
(
// fixing arguments
Instr([\jmcExamples,'moto-rev'],{ arg lfo=0.2,freq=1000,rq=0.1;
RLPF.ar(LFPulse.ar(SinOsc.kr(lfo, 0, 10, 21), [0,0.1], 0.1), freq, rq).clip2(0.4);
});
q = Patch([\jmcExamples,'moto-rev'],[
0.2
]);
q.gui;
)
You can design Instr to take parameters that are used only in the building of the SynthDef. This can be used to select from different kinds of filters or to .
Instr(\upOrDown, {arg upDown=0;
var line;
if (upDown>0,
{line = Line.kr(1,0,5)}, // upDown>0 ==> pitch goes up
{line = Line.kr(0,1,5)} // upDown 0 or less ==> pitch goes down
);
SinOsc.ar(440*line,0,0.2);
},[
StaticIntegerSpec(0,1)
]);
Patch(\upOrDown, [ 0]).play
The upDown param acts as a switch between different synth def architectures. If your Instr library is well designed you can acheive very sophisticated sound structures with automatic optimizations and code reuse.
Note that the Patch would assume upDown to be a modulatable control input (with a default of 0.0) without the StaticIntegerSpec making it clear that its a static integer.
Busses and Groups
(
s.boot;
a = Group.new;
b = Group.after(a);
c = Bus.audio(s,1);
p=Patch({ arg in,ffreq;
// the Bus is passed in as In.ar(bus.index,bus.numChannels)
LPF.ar(in,ffreq)
},[
c,
KrNumberEditor(3000,[200,8000,\exp])
]).play(group: b);
// play something onto this bus in a group before that of the filter
y = Patch({ Saw.ar(400) * 0.1 }).play(group: a, bus: c );
z = Patch({ Saw.ar(500) * 0.1 }).play(group: a, bus: c );
z.bus
z.synth
z.group
z.server
z.stop;
y.stop;
)
// z and y are now stopped by p is still playing
// c is a Bus object
c
// play c ontto a main audio output
c.play
//use command-. to stop all
(
s.boot;
a = Group.new;
b = Group.after(a);
// no index, not yet allocated
c = Bus(\audio,nil,2);
y = Patch({ arg in,ffreq;
LPF.ar(in,ffreq)
},[
c, // a proxy, the bus is yet to be allocated
KrNumberEditor(3000,[200,8000,\exp])
]).play(group: b);
// now that the patch has played, the bus allocated itself
c.insp
// play onto this bus in a group before that of the filter
z = Patch({ Saw.ar([400,401]) * 0.1 }).play(group: a, bus: c )
Automagic setter for the inputs of the Patch
Sending controllers to the Patch while its playing
p = Patch({ arg freq=440; SinOsc.ar( freq ) });
// with no args supplied, a KrNumberEditor was made as input for \freq
p.args.dump;
p.play
// each arg name automagically becomes an attribute of the patch object
p.freq
// set its value
p.freq.value = 300
// look for the input for the 'freq' arg
// and sends it the .set(500) message if it responds to that
p.set(\freq, 500)
Quite apart from Patch, you can use a spec to map a signal from a 0..1 range to the range of the spec :
(
var spec;
spec = [ 100,18000,\exp].asSpec;
{
SinOsc.ar(
// creates a BinaryOpUGen
spec.map( SinOsc.kr(0.1).range(0,1) ).dump
)
}.play
)
you can also do that with a Patch, supplying the resulting function as an input to the patch:
(
var spec;
spec = [ 100,18000,\exp].asSpec;
Patch({ arg freq;
SinOsc.ar(freq)
},[
// creates a BinaryOpFunction
spec.map( { SinOsc.kr(0.1).range(0,1) } ).dump
]).play
)
A Patch is subclass of AbstractPlayer which is a subclass of AbstractFunction.
Because a Player IS A FUNCTION, a Spec may also be used to map another player's output and then use that as an input to a patch :
(
var spec;
spec = [ 100,18000,\exp].asSpec;
Patch({ arg freq;
SinOsc.ar(freq)
},[
// a BinaryOpFunction
spec.map( Patch({ SinOsc.kr(0.1).range(0,1) }) ).debug
]).play
)
spec.map is taking the player
and creating a BinaryOpFunction out of it.
if you do math on functions you get another function.
f = { 3 } * { 4 };
f.value
the simplest example is:
(Patch({ SinOsc.ar(440) }) * 0.1).play
where the output of the Patch is multiplied by 0.1, reducing the amplitude.
Internal optimization in this case results in using a PlayerAmp
// true binary op players not yet implemented :(
(Patch({SinOsc.ar(440) }) % 0.4).play
Spawning
(
a = Patch({
SinOsc.ar(800,0.0)
});
c = Bus.audio;
a.play(bus: c);
// a is now playing on bus c, which we can't hear
// patch b will listen to buss c and play one enveloped grain
b = Patch({ arg tone;
var gate;
gate = Trig1.kr(1.0,0.25);
tone = In.ar(tone,1);
tone * EnvGen.kr(Env([0,1,0],[0.05,0.05],\welch,1),gate,doneAction: 2)
},[
c.index
]);
b.prepareForPlay(s);
// play one grain
b.spawn(atTime: 0.1);
// play 100 grains
Routine({
1.0.wait;
100.do({
b.spawn(atTime: 0.1);
0.25.wait
})
}).play(SystemClock)
)
Extra capabilities
Because the Patch manages the creation of the SynthDef and also manages the Synth that is created at the time of play, it is possible to do some tricks that make client-server communication a bit easier.
Keep in mind that when the sc lang code that is inside a SynthDef or an Instr is run when the the SynthDef or InstrSynthDef is compiled. Its compiled into bytecode that is then sent to the server but then when the synth itself runs there is no sc language. There are no functions or 'if' statements.
So once you have that concept clear in your head, here's how to violate it.
UGen:onTrig(function,value)
trigger.onTrig({ |time,value|
"did trig".postln
})
On receiving a trigger (a non-positive to positive transition) evaluate the function IN THE CLIENT LANGUAGE. A value may also be passed in which will be polled and passed to the function.
(
Patch({ |freq=200|
var out;
out = LFSaw.ar(freq);
(Dust.kr(0.3)).onTrig({ |time,value|
["dusty",time,value].postln
},out); // this value will be polled anytime the trig goes
out * 0.1
}).play
)
(
p = Patch({ |freq=200|
(freq >= 300).onTrig({
"GREATER !".postln
});
LFSaw.ar(freq) * 0.1
});
p.play
)
p.freq.value = 400
p.freq.value = 200
(
p = Patch({ |freq=200|
var lfo;
lfo = LFSaw.kr(0.2);
Dust.kr(2.0).onTrig({ |time,value|
[time,value].postln
},lfo);
LFSaw.ar(freq)
});
p.play
)
You could also spawn more synths.
What is happening is that a ClientOnTrigResponder is added to the InstrSynthDef's stepchildren. When a Patch plays it also plays its children (the inputs to the patch) and its stepchildren. The ClientOnTrigResponder adds an OSCpathResponder when the synth starts and removes it when the synth stops.
see also [InstrGateSpawner]