Hacking chucklib
Please see the ChuckPrototypes help file for a list of the provided process and prototypes, and how to use them.
Users are encouraged to write new components and new process structures. This document provides advice on hooking into the framework for your own evil purposes.
API fundamentals:
A process prototype is a Proto. Hard-coded classes are not permitted. Protos define variables and methods using environment variables:
a = Proto({
~aVariable = 10;
~aMethod = #{ |num| ~aVariable * num };
});
a.aMethod(5);
50
Note: I tend to use closed functions for methods (indicated by the #). The functions are not required to be closed. Also, when you call a pseudo-method in a Proto, you will enter the Proto's environment. This is different from the behavior of functions in environments whose know variable has been set to true.
Only one method is required in a process prototype: asPattern. The job of a process is to render itself as an event pattern to be played by the bound process (BP) container. Thus, a simple, single-purpose process prototype could be written as briefly as:
Proto({
~event = (eventKey: \default);
~asPattern = #{
Pbind(\degree, Pn(Pseries(0, 1, 8), inf), \delta, 0.25, \instrument, \default);
};
}) => PR(\cscale);
PR(\cscale) => BP(\cscale);
SynthDescLib.global.read;
BP(\cscale).play;
BP(\cscale).stop;
The prototype must also contain a key to a ProtoEvent in the ~event variable. This process uses the default event (see Streams-Patterns-Events1 and help files 2-6 for a full description). As part of normal startup, Event.default is saved into ProtoEvent(\default), where it is accessed by eventKey: \default.
This is, of course, a silly example because you gain little in functionality over a regular Pbind. (You do gain some interface building features doing it this way.) The real flexibility of process prototypes is that the pattern can be defined using a variety of methods and objects contained within the Proto. If defined correctly, the behavior of the process can change while the process is playing (something difficult to do with the out of the box pattern classes).
Proto({
~event = (eventKey: \default);
~startRand = 10;
~lengthLow = 3;
~lengthHi = 12;
~step = #[1];
~scaleSeg = #{
Pseries(~startRand.rand, ~step.choose, rrand(~lengthLow, ~lengthHi));
};
~asPattern = #{
Pbind(\degree, Pn(Plazy({ ~scaleSeg.value }), inf),
\delta, 0.25,
\instrument, \default);
};
}) => PR(\cscalemix);
PR(\cscalemix) => BP(\cmix);
BP(\cmix).play;
BP(\cmix)[\step] = #[-1, 1]; // both directions!
BP(\cmix)[\startRand] = 20; // widen range of starting notes
BP(\cmix)[\step] = #[-1, 1, 2]; // allow upward thirds as well
// etc.
BP(\cmix).stop;
BP(\cmix).free;
Because the pattern definition is more modular, it's easy to change the behavior of the pattern just by changing its components in the environment.
To sum up: BP calls ~asPattern to get the process's behavior. ~asPattern can do anything it wants to make the pattern, as long as the output is an event pattern. There is no limit to the complexity of the pattern's back-end construction (though you should not make it so complex that performance suffers).
Changeable component patterns:
Most of the processes allow you to change material while the process is playing. The technique is to maintain a stream in an environment variable. When asPattern builds the event pattern, it doesn't use the stream directly. It uses a routine that grabs the next value from the stream in the environment variable.
The result is that if you replace the stream in the environment variable, the values will come from the new stream.
In a BP environment, assigning a pattern to any environment variable automatically creates a stream for the pattern in a variable named ~[originalName]Stream, where [originalName] is the variable name to which the pattern is assigned. For instance, "~delta = Pwhite(1, 8, inf) * 0.25" assigns the stream to the environment variable ~deltaStream.
In asPattern, use BPStream to access the changeable streams as below. (You may assign constants and other non-pattern values initially and BPStream will retrieve them, but you may replace the stream only by using a Pattern. If you want to replace a stream with a new constant, use Pn(constant, inf) or constant.asPattern.)
Example:
PR(\abstractProcess).clone({
// component patterns
~degree = Pn(Pseries(0, 1, 8), inf);
~delta = 0.25;
~legato = 1.2;
~asPattern = {
Pbind(
// makeProut renders the pattern into a changeable stream
\degree, BPStream(\degree),
\delta, BPStream(\delta),
\legato, BPStream(\legato)
)
};
}) => PR(\flexscale);
PR(\flexscale) => BP(\flexscale);
SynthDescLib.global.read;
BP(\flexscale).play;
// chuck new patterns into the variables
Pxrand((0..7), inf) =>.degree BP(\flexscale);
Pwrand([0.25, Pseq([0.25, 0.125], 1)], [2, 1].normalizeSum, inf) =>.delta BP(\flexscale);
// assignment syntax also works
BP(\flexscale).legato = Pn(Pgeom(0.1, 1.2, 15), inf);
// try writing your own
// when done:
BP(\flexscale).stop;
BP(\flexscale).free;
Inheritance:
You can inherit methods and variables from existing processes (make your own custom subclass) using the following syntax:
PR(\baseProcessName).clone({
// write your new methods and variables here
// if you don't touch a variable from the base process, it will be inherited intact
}) => PR(\subclassName);
PR(\abstractProcess) includes a number of pseudo-methods you will probably want, so you will likely write your prototypes as:
PR(\abstractProcess).clone({
// my stuff here
}) => PR(\myProcess);
Safety checking:
Most processes will require certain information to be in place before they can be rendered into a pattern. Whenever you play a bound process, the environment will run through a list of a symbolic keys, stored in the environment variable ~requiredKeys.
~requiredKeys = #[\mel];
If even one of the environment variables referenced in the required keys array is nil, the process will not begin. It will fail silently.
_________________________
I'm sure this document by itself is not enough, but I'm not sure what questions people are going to have. If you want to know something not covered here, please contact me through my website (http://www.dewdrop-world.net) or ask on the SuperCollider mailing list.