PR(\bufPerc)


Drum machine process that uses sound files loaded into server buffers as the sound source.


This is the basis of the other drum machine processes: defPerc, defTrig and break.


Note: Some of what is presented here might be intimidating at first. That's because it operates at a higher level of abstraction even than patterns. This is a rich process prototype, with lots of parameters and opportunities to customize behavior. The best approach is to start with the simplest usages, and dabble in more complicated behaviors only after getting comfortable with the basics.


The parameter dictionary used at initialization can get rather long. Use a BP Factory to simplify use in performance.


Important: The drum machine processes are not polyphonic and are not meant to layer several drum parts in one process. You should create separate instances of these process prototypes for each drum "part." Another process can coordinate them so that they behave as a unit.


Initialization parameters


This process is designed to use the parameter dictionary in the chucking operation for almost all initialization. The reason is that the process must load buffers during preparation, so the buffers have to be specified within the chuck operation. Once you're doing that, it's a simple matter to set other parameters the same way.


bufPaths: One or more samples are permitted (for example, a high hat process might need open and closed samples). Here, give an array listing paths to the soundfile(s). If you're using only one sample, you still have to wrap the path in an array: ["sounds/a11wlk01.wav"].


bufCoords: By default, the whole of each sound file will be loaded. You can override this array to load portions of each file: [ [startFrame, numFrames], [startFrame, numFrames] ... ] where each array item matches up to the corresponding path.


deltaAdjust: You might need to compensate for different attack durations -- a drum sound with a longer attack must place slightly early to be perceived on time. This array gives offsets in seconds for each buffer: positive to push the sound slightly later, negative to pull it earlier. *


divCycle: The basic division of the beat: the default is 0.25, or 16th notes. You can swing the rhythm by giving an array such as [0.3, 0.2] -- this pattern will be propagated over the entire bar.


beatsPerBar: Self-explanatory. Defaults to 4.


mono: A Boolean flag. If false (the default), successive notes may overlap on the server. If true, notes will be truncated as soon as the next note sounds (this is useful for high hats, e.g.).


compensateEnv: The default synthdefs for this process use a linear attack-sustain-release envelope. If this flag is false (the default), the sustain segment is the specified sustain time (see below). If true, the sustain is shortened by the attack and release times so that the synth's life time will be exactly the specified sustain.


chan: Optional -- if you have a specific MixerChannel already created that you want the process to use for output, specify it here in the parameter dictionary. If you omit this parameter, the following parameters will be used to create a MixerChannel for you.

inChannels: The number of input channels for the MixerChannel.

outChannels: The number of output channels.

master: The target of this process MixerChannel -- may be a global mixer, or it could also be a drum submix.


postMCCreation: A user specified function that executes after creating the MixerChannel for the process. Here, you may initialize other resources, create effects etc. This takes place before loading the sound files. (If you manually supply a MixerChannel as chan, this function still executes.)


postBufferLoad: A user specified function that executes after all buffer loads are complete. Useful for calculations that depend on buffer sizes.


free: If you created any extra resources in postMCCreation, you should supply a function here to release those resources.



* If you're using deltaAdjust to move events earlier, you must set the process' leadTime to allow enough wiggle room. Since the delta adjustment is in seconds, but leadTime is in beats, multiply the largest negative delta adjustment by the tempo; the absolute value of this is the minimum lead time. Preferably it should be just slightly higher.


// Here is one way to calculate the right leadTime.

BP(\drums).leadTime = (BP(\drums).deltaAdjust.abs * clock.tempo).maxItem.roundUp(0.1);



Rhythm array specification


Instead of providing rhythm data as patterns, since this is conceived as a drum machine, a set of arrays provide the primary parameters.


~amps: The amplitude at each subdivision of the beat. 0 indicates a rest. This array should have enough values to account for the entire bar: ~beatsPerBar / ~divCycle.asArray.sum * ~divCycle.asArray.size, by default = 4.0 / 0.25 = 16. The ~ampsSize method returns the result of this calculation.

~rates: Playback rate scaling for each note.

~bufs: A numeric index indicating which buffer to use.

~times: How long (in beats) to sustain the sound. If this array is omitted, the buffer's duration is used.


Of less importance:


~def: The synthdef to render the note. Normally this is just a symbol, to use the same synthdef for every note. Standard synthdefs follow. See the definitions in Prototypes/startup-14.txt; if you want to write your own synthdefs, you should follow the same basic arguments as in these.

\bufGrain: Mono sound file, outputs one channel.

\bufGrain2: Stereo sound file, outputs two channels.

\bufGrainPan: Mono sound file, outputs two channels, pans output using Pan2.

\bufGrainPan2: Stereo sound file, outputs two channels, pans output using Balance2.

\bufGrainRLPF: Like \bufGrain, but adds a resonant lowpass filter.

\bufGrainRLPF2: Like \bufGrain2, but adds a resonant lowpass filter.

\bufGrainRLPFPan: Like \bufGrainPan, but adds a resonant lowpass filter.

~attack: Duration in seconds of the attack portion of the envelope.

~decay: Duration in seconds of the release portion of the envelope.


The ~amps array must always include one element for every subdivision in the bar.


For the other arrays, do not include values for rests. That is, if the ~amps array has five nonzero values, the other parameter arrays should have five elements each, not 16. This reduces the number of events that the event stream must process. This is also strongly recommended when ~mono == true.


// this syntax is for the chucking parameter dictionary:

amps: #[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], // snare on 2 and 4

rates: #[1.2, 1.0], // first snare is slightly higher pitched


If you would rather provide fully padded arrays for all parameters, you can set the ~compactPatterns flag to false. 


// with compactPatterns: false, this should have the same effect as above

compactPatterns: false,

amps: #[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], // snare on 2 and 4

rates: #[0, 0, 0, 0, 1.2, 0, 0, 0, 0, 0, 0, 0, 1.0, 0, 0, 0], // first snare is slightly higher pitched


But there is a better way...



Auto-generating rhythms per bar


All the drum machine processes work by converting the arrays into patterns, once per bar. User hooks are provided so you can insert your own custom logic.


pbindPreAction: A function that executes before converting the arrays to patterns. If you modify the arrays here, you will hear the effect in the next bar.

pbindPostAction: This function receives an array with all the key-value pairs that will be supplied to Pbind: [key0, pattern0, key1, pattern1...]. ("Post" in this case does not mean it executes after creating the Pbind -- rather, the function runs after the data have been converted into a form suitable for Pbind.)


You may assign a literal function to either of these hooks, or you can use a symbol which will be looked up in the Func collection.


See the example below for one usage of pbindPreAction to generate new rhythmic content. (I have yet to use pbindPostAction in an actual composition.)



Rhythm array manipulation functions


It's often easier to generate rhythmic values in terms of a complete, padded matrix, but the compact form is recommended for playback. ("Compact form" means that arrays other than ~amps included items only for sounding notes, not rests.)


The Func collection includes some functions to convert automatically between the two representations, and also manipulate the matrix form.


Func(\expandKeys): Starting with the compact form, convert the arrays into the padded form.

Func(\shrinkKeys): Convert the arrays from the padded form into the compact form.

Func(\initKeys): Reset the arrays to their base (starting) values. Each array can have a different base, given as array name + "base"; if that is not provided, the array at ~base is used. If ~base is absent, a zero-filled array is the final fallback position.

Func(\insertIntoKeys): If the arrays are in padded, matrix form, generate values for the arrays at the given index.


You must set the ~usedKeys variable in the process to indicate which arrays to touch. Some arrays might remain the same, or remain at default values, throughout the process lifetime. By omitting those array names from usedKeys, you can eliminate unnecessary processing.


Func(\insertIntoKeys) uses "generator patterns" to produce the values to insert. Store the pattern in a process variable named after the array + "gen." (See the example that follows.) When the pattern is evaluated, it receives the subdivision index as an argument -- e.g., with 16th-note subdivisions in a 4/4 bar, index 4 represents beat 2 exactly.



Examples:


s.boot;

TempoClock.default.tempo = 2;


// Fixed rhythm, compactPatterns == true

(

PR(\bufPerc).chuck(BP(\ex1), parms: (

bufPaths: #["sounds/a11wlk01.wav"],

bufCoords: #[[85300, 23800]], // [[startFrame, numFrames]]

rates: #[1.2, 1.0, 1.0],

amps: #[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],

times: #[1.5, 0.5, 0.5]

));

)


BP(\ex1).play;

BP(\ex1).stop;

BP(\ex1).free;


// Fixed rhythm, compactPatterns == false

// let's also add random panning per note

// panning is not a standard array parameter: must use ~argPairs

(

PR(\bufPerc).chuck(BP(\ex2), parms: (

bufPaths: #["sounds/a11wlk01.wav"],

bufCoords: #[[85300, 23800]], // [[startFrame, numFrames]]

rates: #[1.2, 0, 0, 0, 0, 0, 1.0, 0, 0, 0, 1.0, 0, 0, 0, 0, 0],

amps: #[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0],

times: #[1.5, 0, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0, 0, 0],

compactPatterns: false,

inChannels: 2,

def: \bufGrainPan,

argPairs: [pan: Pwhite(-1.0, 1.0, inf)]

));

)


BP(\ex2).play;

BP(\ex2).stop;

BP(\ex2).free;



// Auto-generated rhythm, calculating 16-element arrays then shrinking them

// "Popcorn baby jmc"

(

PR(\bufPerc).chuck(BP(\ex3), parms: (

bufPaths: #["sounds/a11wlk01.wav"],

bufCoords: #[[85300, 23800]], // [[startFrame, numFrames]]

amps: 0,

usedKeys: #[rates, times],

pbindPreAction: {

var pool = (3..15).scramble;

~amps = (0 ! 16).put(0, 1);

~rates = (0 ! 16).put(0, 1);

~times = (0 ! 16).put(0, 1.5);

rrand(3, 7).do({ |i|

~amps[pool[i]] = rrand(0.2, 0.5);

~rates[pool[i]] = rrand(1.1, 1.25);

~times[pool[i]] = rrand(0.1, 0.4);

});

Func(\shrinkKeys).value;

}

));

)


BP(\ex3).play;


// What's the audible difference between these settings?

BP(\ex3).mono = true;

BP(\ex3).mono = false;


BP(\ex3).stop;

BP(\ex3).free;



// Auto-generated rhythm using array manipulation functions

// Sounds the same but is written in a more functional, less imperative style

(

PR(\bufPerc).chuck(BP(\ex4), parms: (

bufPaths: #["sounds/a11wlk01.wav"],

bufCoords: #[[85300, 23800]], // [[startFrame, numFrames]]

amps: 0,

usedKeys: #[amps, rates, times],

ampsbase: (0 ! 16).put(0, 1),

ampsgen: Pwhite(0.2, 0.5, inf),

ratesbase: (0 ! 16).put(0, 1),

ratesgen: Pwhite(1.1, 1.4, inf),

timesbase: (0 ! 16).put(0, 1.5),

timesgen: Pwhite(0.1, 0.4, inf),

base: 0 ! 16,

pbindPreAction: {

var pool = (3..15).scramble;

Func(\initKeys).value;

rrand(3, 7).do({ |i|

Func(\insertIntoKeys).value(pool[i]);

});

Func(\shrinkKeys).value;

}

));

)


BP(\ex4).play;

BP(\ex4).stop;

BP(\ex4).free;


In this final example, the parameter dictionary is the longest of any of the examples; but, what you lose in brevity, you gain in flexibility. In the third example, the only way to change how the rhythms are automatically generated is to replace the pbindPreAction function. Exposing more parameters allows you to change the arrays starting values as well as the possible values inserted into them, without touching the function. This might actually be easier to deal with in a live coding situation, for instance.