PR(\macroRh)


The primary chord arpeggiation process in chucklib.


'macroRh' is a peculiar name, but stems from the fact that there are two concepts of rhythm in arpeggiation: the rhythm of chord changes, which is at a higher level and is correspondingly called "macrorhythm," and the rhythm of the output notes, which is faster (lower level), thereby "microrhythm." In PR(\macroRh), the macrorhythm is generated by a pattern; its companion process, chTop, derives macrorhythm from a melody.


Note: Code in this help file is intended for illustration only. Proper execution depends on resources that will not be created in this file. For a working example, see ChuckExamples.


Initialization


Because the chord arpeggiator is a nested process, initialization takes several steps. You can simplify performance initialization using a factory, or use a special-purpose Func that sets all the basic parameters in one operation. First I will describe manual initialization; the constructor Func follows at the end.


The nested process has two layers. The inner layer manages the chord notes and streams them out according to values received from the outer layer, which evaluates parameters per chord (not per note). When creating a nested process, always chuck the inner layer into the BP first, then wrap the outer layer around it.


// Inner layer: note generator

PR(\arpeg1) => BP(\chord);

// Outer layer: chord parameters

PR(\macroRh) => BP(\chord);


After this, BP(\chord) contains the environment variables and methods for PR(\macroRh), and PR(\arpeg1)'s objects have been moved into BP(\chord).child.


Initialization: chord forms


Chord data come from a MIDIRecBuf. The MIDI data are split into separate chords by calculating the average note duration and splitting where the note duration is greater than the average. Playing block chords on a MIDI keyboard works for this; if you program the data in code, I suggest the following:


MIDIRecBuf(\triads, [

[60, 64, 67,  62, 67, 71],

// average is 8/6 = 1.333; chords split at 2's for groups of 3 notes

[1, 1, 2, 1, 1, 2],

1, 0.5

].asNotes, (type: \ch, mode: \cmaj)) => MBM(0);


As explained below, these notes may be adjusted if desired to produce a better fit over the current root note and below a top note. So, you might want to think of them as "chord forms" rather than specific chords -- because they establish certain pitch or interval relationships that inform the final note choice.


Chuck the chord data using the "ch" adverb or by setting type: \ch in the MIDI buffer properties.


// .ch may be omitted because the buffer states type: \ch

MBM(0)[\triads] =>.ch BP(\chords);


Initialization: required parameters of chord changes


These parameters are not optional! Each parameter is identified here by the name you will actually use in code.


macro: A pattern returning either a simple number indicating how many beats should elapse before the next chord, or a two element array. If an array, the first element is the number of beats until the next chord, and the second element is the number of beats that the chord pattern should play. (One chord may stop before the next begins, or even overlap.)

arpeg: A pattern returning the symbolic names of ArpegPat objects, which stream the notes out in arpeggiation order.

micro: A pattern returning the symbolic names of MicRh objects, which provide the note-level rhythm (microrhythm).


The microrhythm is always chosen after the arpeggiation pattern, so you can intelligently choose a microrhythm that fits the arpeggiation pattern, supporting variety while allowing you to avoid inappropriate choices.


// whole-bar chord changes

Pwhite(1, 4, inf) * 4.0 =>.macro BP(\chords);


// chords may be block chords or upward arpeggiations with no repetitions

Prand([\block, \up1], inf) =>.arpeg BP(\chords);


// block chords need a different microrhythm than note-streamers

// so we check which arpegType has already been put into the result event

Pfunc({ |inEvent| (inEvent[\arpegType] == \block).if({ \blockFollow },

{ ['16th', 'sine'].choose })

}) =>.micro BP(\chords);


Since arpeg and micro selection patterns may be complex, you can define them as Pdefn's in your setup code, then reference the pattern by name during performance, e.g.,


// in setup code

Pdefn(\micRhBlockCheck, Pfunc({ |inEvent| (inEvent[\arpegType] == \block).if({ \blockFollow },

{ ['16th', 'sine'].choose })

}));


// in performance -- this actually chucks in the Pfunc

\micRhBlockCheck =>.micro BP(\chords);



FitFunc: adapting chord notes to harmonic context


A chord process can be sensitive to the harmonic context established by other processes. To do this, it has to modify the source notes for maximal consonance over a root note (see aiBass for one way to set the root). Func objects define the algorithms that perform this adaptation. Specify which Func to use in the child's fitFunc variable. Its value may be a symbol, or a function that returns a symbol.


// Fit the notes by rearranging the intervals between the input notes

BP(\chords).child.fitFunc = \chordFitInt;


The following Funcs are available out of the box:


\asis: No adjustment -- use the input notes as they are.

\chordFitInt: Check all possible arrangements of the intervals between input notes, and return the best fit.

\chordFitNotes: Check all transpositions of the input notes against the top note (see next section), and return the best fit.

\chordRandInt: Rearrange the intervals randomly; do not check for consonance.

\fitToBass: Transpose the notes so that the lowest chord note matches the root. No consonance check.

\fitToTop: Transpose the notes so that the highest chord note matches the top note. No consonance check.



Fitting to bass


A chord process needs to know the bass ID in the global library (see aiBass).


BP(\chords).bassID = \roots;


You can also control how the chord process should respond if the root note changes in the middle of an arpeggiation. If ~bassUpdate is true, the chord notes will be reevaluated against every new bass note. If false, the same notes will be kept until the next chord event, even if they are not the optimal fit for the new bass note.



Top note melody: continuity


A chord process may hold a melodic process. If present, one melody note is extracted from it per chord, where might be used by the chord fit Func.


The simplest way to create the top note melody is to chuck a MIDI buffer containing melody data into the chord process:


aMIDIRecBuf =>.mel BP(\chords);

adaptationNoteMIDIRecBuf =>.adapt BP(\chords);


From there, you can set all the properties of the aiMel process by referring to BP(\chords).topNote.


\absSplice =>.adapt BP(\chords).topNote;

BP(\chords).topNote.eugTest = \eugRangeOnlyTest;

BP(\chords).topNote.range = NumericRange(28, 45);


If you want to generate the top notes algorithmically, you can manually assign an instance of PR(\patternTop).


BP(\chords).topNote = PR(\patternTop).copy.make({

~deg = /* pattern returning modally-mapped note numbers */;

~mode = /* symbolic name of Mode() object to use */

});


In PR(\patternTop), you can also specify patterns for ~delta and ~length, but they are ignored by PR(\macroRh). They are meant for use with PR(\chTop) -- see the chTop help file.



ArpegPat and MicRh


The ArpegPat and MicRh storage objects hold functions, which take arguments from the chord process and output patterns with specific return values. See examples in Prototypes/startup20-basicChordBits.txt. Both types of function receive the entire parent event, and may use the data in it to influence the end result.


// ArpegPat pattern should yield the note objects in the 'notes' array

{ |notes, parentEvent| Pxrand(notes, inf) } => ArpegPat(\myArpeg);


// MicRh pattern should yield three-element arrays:

// [delta, sustain (in beats), gate value (velocity)]

// arpegPatResult is the note-stream pattern

// call estimateLength to make a rough guess how many events it will return

{ |arpegPatResult, parentEvent|

Prand(#[0.25, 0.5, 0.75], inf).collect({ |delta|

[delta, delta * rrand(0.8, 1.2), rrand(0.2, 0.8)]

})

} => MicRh(\multiple16th);


// example output

p = ArpegPat(\myArpeg).asPattern([60, 64, 67]).asStream;

p.nextN(10);


p = MicRh(\multiple16th).asPattern.asStream;

p.nextN(10).do(_.postln) // each row is delta, note-sustain, gate



Mode handling


Normally, all of the chord forms will be based on the same Mode (or ModalSpec), and the root and top note processes use that mode also. That's the simplest case.


For better handling of chromaticism, you might want each chord to use a different mode. The quickest way to do this is with a composite mode (see the Mode help file). For each chord, the best mode in the composite set will be chosen.


You should also set the useOwnMode property to true; otherwise, the mode assigned to the chord process as a whole will take effect.


// (You have to define the cmaj, gmaj and emaj modes before doing this.)

[\cmaj, \gmaj, \emaj] => Mode(\composite);

aMIDIRecBuf.chuck(BP(\chords), \ch, parms: (mode: \composite, useOwnMode: true));


You can also manually override the mode for a particular chord object:


BP(\chords).child.chords[0].modeOverride = \abmaj;



Arbitrary synth parameters


You can assign a list of arbitrary parameters to the ~argPairs variable in the child. Those parameters will be passed on to each note.


// Pan each note individually.

BP(\chords).child.argPairs = [pan: Pwhite(-1.0, 1.0, inf)];



One-step initialization


A couple of Func objects are provided to set the most important parameters in one step. The difference is that newCh frees the target BP before creating the new one -- very useful in development because you always start from a clean slate.


The arguments are the same for both functions, and they both return the resulting BP.


Func(\makeCh).value(newBPname, childName, parentName, chordMIDIBuf, topMelodyMIDIBuf, macrorhythm, microrhythmSelector, arpegPatSelector, adaptKeysForTopMelody, mode, parms);


Func(\newCh).value(newBPname, childName, parentName, chordMIDIBuf, topMelodyMIDIBuf, macrorhythm, microrhythmSelector, arpegPatSelector, adaptKeysForTopMelody, mode, parms);


newBPname: A symbol, naming the result BP.

childName: The name (symbol) of the child PR; usually \arpeg1.

parentName: The name of the parent PR, e.g. \macroRh or \chTop.

chordMIDIBuf: The MIDIRecBuf containing chord data.

topMelodyMIDIBuf: The MIDIRecBuf containing melody data for the top note process.

macrorhythm: A pattern that produces macrorhythm values (delta, length).

microrhythmSelector: A pattern returning symbolic names to choose the microrhythm (MicRh) per chord.

arpegPatSelector: A pattern returning symbolic names to choose the arpeggiation pattern (ArpegPat) per chord.

adaptKeysForTopMelody: A pattern returning the names of Funcs to generate new top note melody variations.

mode: The overall mode for this process. Can be the symbolic name of a Mode object.

parms: Any additional parameters, especially those associated with MIDI data.


It is usually not possible to set up a chord process completely using this method, but it gets you fairly close and reduces the number of individual statements you have to make.