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]