- [Topic]
- Patterns
Patterns are objects that generate data according to pattern specific ordering rules. Patterns can hold any sort of Lisp data and may include nested sub-patterns that define smaller (local) orderings within the surrounding pattern.
Pattern Classes
The system provides a number of generic pattern types that can used individually or nested together to form composite patterns:
Table 1. Generic pattern classes.
Pattern | Description |
---|---|
cycle | loops over elements in a continuous cycle |
line | enumerates sequentially and sticks on last element |
palindrome | generates elements forwards and backwards |
heap | randomly permutes elements |
weighting | selects elements from a weighted distribution |
markov | returns nth order Markov chains of its elements |
graph | generates elements from a directed graph |
thunk | computes elements by function call |
rotation | returns systematic permutations of its elements |
rewrite | generates elements by rewrite rules |
There are also a few specialized pattern classes that implement more specific, or restricted, functionality:
Table 2. Specialized pattern classes.
Pattern | Description |
---|---|
copier | copies and repeats periods of a sub-pattern |
range | iterates numbers in a range |
join | merges patterns together into a single stream of data |
transposer | transposes and optionally inverts and reverses pattern data |
chord | returns lists of chord data |
pval | evaluates a pattern value |
Generic Pattern Initializations
The pattern classes listed in Table 1 all support the following slot initializations:
-
:of
data -
Specifies the element or list of elements to generate from the
pattern. This data can include sub-patterns. The
:of
initialization has several aliases that parse the associated pattern data into musical information::notes
data- Returns note names from data. If data contains notes then octave numbers only need to be added when they change from the previous note. An optional mode or tuning may be specified using the :through or :in pattern initializations as appropriate to the note function.
:keynums
data- Returns key numbers from data. An optional mode or tuning may be specified using the :from, :through or :in pattern initializations as appropriate to the keynum function.
:hertz
data-
Like
:notes
but returns hertz values from data. :rhythms
data- Parses logical rhythms into rhythmic values. An optional tempo factor may be specified using the :tempo pattern initialization, as appropriate for rhythm function.
:amplitudes
data- Parses logical amplitudes into amplitude values.
-
:for
{number | pattern} - Sets the period length of the pattern to number or pattern of numbers. The period length determines the size (number of elements) in each period, or chunk, of elements that the pattern generates.
-
:repeat
number - Sets an optional repetition limit for the pattern. Once the limit has been reached the pattern will return an end of data marker instead of elements.
-
:name
{string | symbol} - Sets the name of the pattern to string or to the print name of symbol. A named pattern can be recalled as a "motive" inside a surrounding pattern by fetching the object from its name using the #& read macro or the find-object function.
-
:returning
function - An optional function to apply to each element as it is generated from the pattern. The function is passed one argument, the element, and should return the value to substitute in its place as the value returned from the pattern.
-
:eop-hook
function - An optional function (thunk) to call each time the period is reset. The function is passed no arguments and any return values are ignored.
Consult the dictionary entries of each pattern for additional keyword initializations specific to each class.
Working with Patterns
Use the new
macro or the
generic function make-instance
to create patterns and
the next function to read successive
elements from them:
Example 1. Creating patterns and reading their elements.
(define pat1 (new cycle :keynums '(a4 b c5 d))) (next pat1) ⇒ 69 (next pat1) ⇒ 71 (next pat1 2) ⇒ (72 74) (next pat1 #t) ⇒ (69 71 72 74) (define (play-pat times knums durs amps tmpo) (process repeat times for k = (next knums) for r = (* (next durs) tmpo) for a = (next amps) output (new midi :time (now) :keynum k :duration (* r 1.5) :amplitude a) wait r)) (events (play-pat 20 pat1 .125 .3 1) "test.mid") ⇒ "test.mid"
The next example demonstrates the use of sub-patterns in pattern building. The pat1 pattern randomly chooses between two sub-patterns: a cycle of white notes and a heap, or "shuffling", of black notes. The pat2 pattern is a rhythmic cycle looping over two elements, the symbol e (eighth note) and a line pattern that generates four sixteenths (s) in succession:
Example 2. Patterns and sub-patterns.
(define pat1 (new weighting :of (list (new cycle :keynums '(a4 b c5 d)) (new heap :keynums '(gs4 as cs5 ds))))) (define pat2 (new weighting :rhythms `(e ,(new line :rhythms 's :for 4)))) (events (play-pat 60 pat1 pat2 .1 .5) "test.mid") ⇒ "test.mid"
Since make-instance
evaluates its first
argument (new does not) the class of
the pattern it creates can vary at runtime:
Example 3. Creating patterns programmatically.
(define (play-pats pats trope reps rate) (process with dur = (* rate 2.5) repeat reps for len = (pick 8 12 16) for pat = (make-instance (next pats) :keynums trope :for len) each k in (next pat #t) as x from 0 by rate output (new midi :time (+ (now) x) :keynum k :duration dur) wait (* rate len))) ;;; a pattern of pattern class names (define pcns (new weighting :of '((heap :weight 2) line cycle palindrome rotation)) (events (play-pats pcns '(a4 b c5 d) 12 .1) "test.mid") ⇒ "test.mid"
The play-pats
process creates patterns by selecting a
class from a pattern of classes and then
calling make-instance
on the selected pattern class to
create the pattern. The process then iterates over one period of the
pattern and outputs a midi note each element.
The preceding three examples all illustrate that the pattern accessor next can read single elements, specified numbers of elements or whole periods of elements from a pattern, and that the period length and the number of elements in a pattern do not have to be the same. The period length can be any non-negative integer (including zero) or a pattern of integers, in which case a new length is chosen from the pattern for each new period. If a sub-pattern ever sets its period length to zero, it "disappears" inside the surrounding pattern until it is selected again. By changing period lengths dynamically even very simple pattern types can produce interesting effects.
In the next example only cycle patterns are used but the sub-patterns vary their lengths according to different cycles. The overall effect is a bit like a "musical mobile" in which the cycles of elements rotate about each other in different orbits.
Example 4. Period length patterns.
(define pat1 (new cycle :of (list (new cycle :notes '(a4 b c5 d) :for (new cycle :of '(4 3 2 1 0))) (new cycle :of '(e4 e5 e6 e3) :for (new cycle :of '(0 1 2 3))) (new cycle :of '(f5 ef4) :for 1)))) (define pat2 (new weighting :rhythms `(e ,(new line :rhythms 's :for 4)))) (events (play-pat 80 pat1 pat2 .4 .75) "test.mid") ⇒ "test.mid"
The repeat factor of a pattern establishes a limit on the number of periods that a pattern can generate. Once a pattern reaches this maximum it will return an end of data marker for all subsequent calls to next. By using the eod? and eop? functions, a process can treat the repeat factor and the period length as markers to control musical evolution, without knowing their actual values.
Example 5. Checking for end-of-period and end-of-data conditions.
(define (play-trope pat trope reps rate amp) ;; play trope for reps number of periods and make the first ;; note in each period louder than the others. (let ((p (make-instance pat :notes trope :for (new weighting :of '(1 2 3 4 5)) :repeat reps))) (process with a = amp for n = (next p) until (eod? p) ; stop at end of data output (new midi :time (now) :keynum n :amplitude a :duration (* rate 2.5)) ;; if end of period, make the next note loud when (eop? p) set a = amp else set a = (* amp .6) wait rate))) (events (play-trope 'cycle '(a4 b c5 d) 30 .1 .8) "test.mid") ⇒ "test.mid" (events (list (play-trope 'heap '(a3 b c4 d e f g a) 80 .1 .7) (play-trope 'cycle '(a4 b c5 d e ) 70 .1 .7) (play-trope 'cycle '(a5 b c6 d) 60 .1 .8)) "test.mid" '(0 4 10)) ⇒ "test.mid"
See also:
pattern
[Macro]- Generic pattern classes
- Specialized pattern classes
process
[Macro]