[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.

PatternDescription
cycle loops over elements in a continuous cycle
lineenumerates sequentially and sticks on last element
palindromegenerates elements forwards and backwards
heaprandomly permutes elements
weightingselects elements from a weighted distribution
markovreturns nth order Markov chains of its elements
graphgenerates elements from a directed graph
thunkcomputes elements by function call
rotationreturns systematic permutations of its elements
rewritegenerates elements by rewrite rules

There are also a few specialized pattern classes that implement more specific, or restricted, functionality:

Table 2. Specialized pattern classes.

PatternDescription
copiercopies and repeats periods of a sub-pattern
rangeiterates numbers in a range
joinmerges patterns together into a single stream of data
transposertransposes and optionally inverts and reverses pattern data
chordreturns lists of chord data
pvalevaluates 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: