Chord functions

+ Associated example files

close


+ Preparing instruments for playing chords

The user can indicate that slippery chicken is to place an automatically generated chord at a given point in the piece by enclosing the corresponding number from the pitch-seq curve in parentheses.

:pitch-seq-palette ((1 (3) 2 3 (5) 4 6 3))

When slippery chicken encounters a pitch-seq number in parentheses, it first checks to see whether the given instrument is capable of playing chords. An instrument can be designated as able to play chords by setting the chords slot to T in the definition of the instrument object, as seen here:

(piano
 (:staff-name "piano" :staff-short-name "pno"
  :lowest-written a0 :highest-written c8 
  :clefs (treble bass double-treble double-bass) :starting-clef treble
  :chords t :chord-function piano-chord-fun
  :largest-fast-leap 9
  :microtones nil 
  :midi-program 1))

If an instrument is able to play chords, slippery chicken requires a chord-generating function that determines how pitches are selected from the current set when making chords for that instrument. The user can specify which chord-generating function slippery chicken is to use for the given instrument by passing the variable to which that function is assigned to the chord-function slot of the given instrument object, as seen above.

close

+ The default-chord-function

If the chords slot is set to T but the chord-function is left unspecified, slippery chicken will select the pitches for that instrument's chords using the default-chord-function, as defined in the instrument class.

The default-chord-function generates two-note chords consisting of the pitch that would be generated by the number in parentheses in the pitch-seq curve and the next lower pitch from the set that is available to the instrument at that time. If no lower pitches are available, the function chooses the next higher pitch instead. If neither of these options can be fulfilled, it returns the original single pitch.

Using the default-chord-function with the vibraphone of the +slippery-chicken-standard-instrument-palette+ and the following set-palette, rthm-seq and associated pitch-seq:

:set-palette '((1 ((c4 d4 e4 f4 g4 a4 b4 c5))))
:rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                        :pitch-seq-palette ((1 (2) 3 4 (5) (6) (7) 8)))))

…produces the following printable output:

chords-default.png

close

+ Predefined chord functions

A number of chord functions have been predefined in the file instruments.lsp and are loaded automatically with slippery chicken. Some of the instrument objects in the +slippery-chicken-standard-instrument-palette+ already make use of these functions, including the piano, guitar, violin, viola, and cello objects.

The piano-chord-fun

The definition of the piano instrument object in the preparing instruments for playing chords section above, for example, indicates that slippery chicken is to use the piano-chord-fun. That function generates four-note chords from consecutive notes in the current set, where possible, with the number enclosed in parentheses in the pitch-seq being the top note of that chord, where possible.

Using the piano-chord-fun with the piano of the +slippery-chicken-standard-instrument-palette+ and the following set-palette, rthm-seq and associated pitch-seq:

:set-palette '((1 ((c4 d4 e4 f4 g4 a4 b4 c5))))
:rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                        :pitch-seq-palette ((1 (2) 3 4 (5) (6) (7) 8)))))

…produces the following printable output:

chords-piano.png

NB: The major-second clusters in the above example are the result of this function being applied to a set consisting of only major seconds. If the consecutive pitches in the set were to have larger intervals between them, the resulting chords would reflect this in their interval structure.

The chord-fun1 and chord-fun2 functions

Two other basic predefined chord functions, namely chord-fun1 and chord-fun2, can also be found at the bottom of the instruments.lsp file.

None of the instrument objects in the +slippery-chicken-standard-instrument-palette+ use either of these functions by default. The user can choose to use one of these as the chord function for a given instrument by setting the corresponding instrument object's chord-function slot using the set-slot method (see the section on changing instrument attributes temporarily for more detail):

(set-slot 'chord-function
          'chord-fun1
          'piano
          +slippery-chicken-standard-instrument-palette+)

The chord-fun1 function generates three-note chords where possible, using every second pitch from the list of pitches currently available to the given instrument from the current set and ensuring that none of the chords it makes span more than an octave.

Using the chord-fun1 function with the piano of the +slippery-chicken-standard-instrument-palette+ and the following set-palette, rthm-seq and associated pitch-seq:

:set-palette '((1 ((c4 d4 e4 f4 g4 a4 b4 c5))))
:rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                        :pitch-seq-palette ((1 (2) 3 4 (5) (6) (7) 8)))))

…produces the following printable output:

chords-chord-fun1.png

The chord-fun2 function generates 4-note chords where possible, using every third pitch from the list of pitches currently available to the given instrument from the current set, with no limit on the total span of the chord.

Using the chord-fun2 function with the piano of the +slippery-chicken-standard-instrument-palette+ and the following set-palette, rthm-seq and associated pitch-seq:

:set-palette '((1 ((c4 d4 e4 f4 g4 a4 b4 c5 d5 e5 f5 g5 a5 b5 c6 d6 e6))))
:rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                        :pitch-seq-palette ((1 (3) 5 7 (11) (13) (15) 17)))))

…produces the following printable output:

chords-chord-fun2.png

These two functions make use of the chord-fun-aux function, which can be very handy for users who want to create moderately tailored chord selection functions with only a few standard arguments, as is described in more detail below.

close

User-defined chord functions

+ The chord-fun-aux function

The chord-aux-fun is a function that can be used to create moderately tailored, user-defined chord functions.

The arguments

All internal pitch-selection processes in slippery chicken require the arguments curve-num, index, pitch-list, pitch-seq, instrument, and set. For that reason, all user-defined chord functions must also start by specifying these arguments but they can safely be ignored and very often are even by slippery chicken's chord functions (they are passed for the sake of completeness). default-chord-fun describes/uses these arguments as follows:

  1. The current number from the pitch-seq. Currently ignored by default.
  2. The index that the first argument was translated into by the offset and scaler (based on trying to get a best fit for the instrument and set). This can be assumed to be a legal reference into pitch-list as it was calculated as fitting in pitch-seq's get-notes. (zero-based.)
  3. The pitch-list created from the set, taking into account the instrument's range and other notes already played by other instruments.
  4. The current pitch-seq object. Currently ignored by default.
  5. The current instrument object. Currently ignored by default.
  6. The current set object. Currently ignored by default.
(defun new-chord-function (curve-num index pitch-list pitch-seq instrument set)…

The chord-fun-aux function adds three new arguments to this list, namely skip, num-notes, and max-span:

(defun chord-fun-aux (curve-num index pitch-list pitch-seq instrument set
                      skip num-notes max-span)…

All three arguments take integers as their values.

The chord-fun-aux is designed to collect pitches from the segment of the current set that is available to an instrument when the chord is made (i.e. the pitch-list argument) by passing consecutively through each pitch of that list. The skip argument allows the user to design functions that skip some of these notes. Setting this argument to 1 results in no pitches being skipped and chords being created from consecutive pitches in the pitch-list. A value of 2 for this argument takes every second pitch from the segment of the set, a value of 3 every third, etc. Thus, if the pitches available to an instrument at a given moment are (C4 E4 F4 A4 B4 D5), and skip is set to 2, a three-note chord starting on C4 will be made from pitches (C4 F4 B4).

The argument num-notes determines the number of pitches the function will try to place in each chord. If the list of pitches available to an instrument is too short to make a chord with x notes, a chord with fewer pitches may be made instead.

The max-span argument designates the largest interval (in semitones) allowed between the bottom and top notes of the chord. If a chord made with the specified number of notes surpasses this span, a chord with fewer pitches may be made instead.

Making a new chord function using chord-fun-aux

New chord functions based on chord-fun-aux are made using the usual Lisp defun macro.

(defun new-chord-function (curve-num index pitch-list pitch-seq instrument set)
  (chord-fun-aux curve-num index pitch-list pitch-seq instrument set 4 3 14))

Any user-defined chord function must be defined before the slippery-chicken object is made, so that the make-slippery-chicken function knows how to choose the pitches for the chords.

The user first specifies the name of the new function and lists the six arguments required for all note functions mentioned above. In the body of the new function, the user then calls chord-fun-aux, listing the first six arguments again and specifying integer values for the three new arguments.

For example, the new-chord-function defined above creates a new function that makes chords from every fourth pitch in the segment of the current set that is available to the instrument, aiming to make three-note chords where possible, with a maximum span of 14 semitones between the bottom and top note of the chord.

Applying this function to the piano instrument of the +slippery-chicken-standard-instrument-palette+ with the following set-palette, rthm-seq, and associated pitch-seq:

:set-palette '((1 ((c4 d4 e4 f4 g4 a4 b4 
                    c5 d5 e5 f5 g5 a5 b5))))
:rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                        :pitch-seq-palette ((1 (14) (13) (12) 11 10 9 8))))) 

…produces the following printable output:

chords-new-fun.png

close

+ A breakdown of the chord-fun-aux function

Since slippery chicken provides users with the flexibility of defining their own chord selection functions, an introduction to how a fully separate chord function may be programmed is given here by examining the chord-fun-aux function in more detail.

(defun chord-fun-aux (curve-num index pitch-list pitch-seq instrument set
                      skip num-notes max-span)
  (declare (ignore set instrument pitch-seq curve-num))
  (unless (and (integer>0 skip) (integer>0 num-notes) (integer>0 max-span))
    (error "slippery-chicken::instruments:: skip, num-notes, and max-span must
            be integers > 0"))
  (let* ((start (max 0 (- index (- (* skip num-notes) skip))))
         (at-start (nth start pitch-list))
         (result (list at-start)))
    (loop 
       repeat num-notes
       for i from start by skip
       for p = (nth i pitch-list)
       do
         (when (and p 
                    (<= (pitch- p at-start) max-span)
                    (not (member p result :test #'note=)))
           (push p result)))
    (if (> (length result) 1)
        (make-chord result)
        (first result))))

All chord functions must be defined with the same six required arguments:

(defun chord-fun-aux (curve-num index pitch-list pitch-seq instrument set

chord-fun-aux adds three more arguments to this list:

                      skip num-notes max-span)

Not all of the arguments must be used (see the default-chord-function page for details on the arguments). Common Lisp will print a WARNING by default for any arguments that aren't used. These warnings can be suppressed by using the declare and ignore functions with the unused arguments:

  (declare (ignore set instrument pitch-seq curve-num))

In addition to its three new arguments, the only required arguments this function makes use of are index and pitch-list. The pitch-list is the automatically generated sublist of pitches from the current set that are available to the instrument at that point in the piece. This is different from the set argument, which gives the user access to all of the pitches in the current set, regardless of any limiting factors. (See the documentation on how slippery chicken selects pitches for more detail.) The value of the index argument is also determined internally by slippery chicken as part of the automatic pitch selection process. It refers to a pitch within the pitch-list.

The function then tests to make sure that the user hasn't passed a value of 0 to any of the three new arguments, and exits with an error if any of these values are 0:

  (unless (and (integer>0 skip) (integer>0 num-notes) (integer>0 max-span))
    (error "slippery-chicken::instruments:: skip, num-notes, and max-span must
            be integers > 0"))

Three variables are then declared for the function. The first, start, determines the index number for the first pitch in the chord.

  (let* ((start (max 0 (- index (- (* skip num-notes) skip))))

It does so in four concise steps: It first multiplies the skip value by the num-notes value and then subtracts the skip value from the result to get the ideal index for the highest note in the chord. It then subtracts that result from the value of the index argument to get the ideal index for the lowest note of the chord, since the index number is to be for the top note of the chord. Its last step is to compare this result with 0 and choose the higher value, so as not to generate an illegal (i.e. less than zero) reference into a list. That value is the index of the first (lowest) note in the chord.

The second variable is for the first pitch itself. It gets the pitch located at the position start in the pitch-list and assigns it to the variable at-start:

         (at-start (nth start pitch-list))

The final variable, result, is for the list of pitches that will make up the chord. This list is initiated with just the first single pitch of the at-start variable, and will be expanded by new pitches as the function takes its course:

         (result (list at-start)))

The function then begins a loop that is to repeat the number of times specified by the value of num-notes:

    (loop 
       repeat num-notes

The first time through the loop the value of i is set to the value of start. Each successive time through the loop it increases the value of i by the value assigned to the skip variable:

       for i from start by skip

Each time through the loop it sets the variable p to the pitch located at position i in the pitch-list:

       for p = (nth i pitch-list)

For each pitch p it runs three tests. It first checks to make sure that p indeed has a value (is not NIL); then it checks to see if the result of subtracting the at-start pitch from p (using the method pitch-) returns a number (semitones) less than or equal to the value of max-span; and finally it checks to see that pitch p is not already present in the result list. When all of these conditions are met, it adds p to the result list:

       do
         (when (and p 
                    (<= (pitch- p at-start) max-span)
                    (not (member p result :test #'note=)))
           (push p result)))    

Now that the pitches have been collected and added to the result list, the method first double-checks to make sure that the result list has more than one pitch in it. As long as this is the case, the function creates a chord from the pitches in the result list using the make-chord function. If the result list has only one pitch in it, the function just returns that single pitch.

    (if (> (length result) 1)
        (make-chord result)
        (first result))))

This provides a powerful approach to algorithmically generating chords as chord selection for a whole piece can be refined through careful management of one simple function. For example, in you are coming into us who cannot withstand you by Michael Edwards, the muddiness created by the default piano chord, which selected low minor thirds and major sevenths, was avoided by modifying the piano chord selection function to select fourths when below a certain pitch. In this way the harmonic character of the piano in the whole piece could be changed in one fell swoop.

close

Chord functions and set-palette subsets

+ Example 1—One subset chord per set

One possible approach to writing user-defined chord functions without the use of the chord-fun-aux is to define subsets (and/or related-sets) in the sets that are accessed by the function to create chords. This can be done without the use of a subset-id in the corresponding instrument object, since any user-defined function can have access to the entire set, as described above. The examples here employ that approach. (More information on how to use an instrument object's subset-id slot can be found on the pitches page.)

One option could be to define one single chord that is to be used any time a chord indication is encountered for a given set. As mentioned in the section on chord-fun-aux above, any chord function must be defined before the slippery-chicken object is made, so that the make-slippery-chicken function knows how to choose the pitches for the chords. Such a function could be defined as follows:

(defun piano-subset-fun (curve-num index pitch-list pitch-seq instrument set) 
  (declare (ignore curve-num index pitch-list pitch-seq instrument))
  (let* ((ss (when (subsets set) (get-data 'piano-chord (subsets set) nil)))
    (when ss 
      (make-chord (data ss)))))

This function ignores all of the required note-getting arguments except for set. It then declares the variable ss and says that its value will be determined by getting the data associated with the ID piano-chord in the subsets slot of the current set object. The nil is for the optional argument of the get-data method and indicates that no warning will be printed if there is no subset named piano-chord. The function then checks to see if ss now has a value, and if so, it uses the data contained in ss (i.e., the list of pitches from the piano-chord subset) to make a chord.

NB: As a result of accessing set rather than pitch-list, no values assigned to set-limits-high or set-limits-low will be taken into account. See the section on set-limits-high and -low for more on that feature. The set-limits could be taken into account here by first determining the pitch-intersection of the subset and the pitch-list.

Once the function is defined, it must be assigned to the instrument that is to use it. That is done here using the set-slot method and assigning it to the piano instrument of the +slippery-chicken-standard-instrument-palette+:

(set-slot 'chord-function 'piano-subset-fun 'piano 
          +slippery-chicken-standard-instrument-palette+)

Now whenever slippery chicken encounters a chord indication for the piano instrument, it will always make the chord from the pitches in the subset of that set with the ID piano-chord.

(let* ((ss-chords-piece-1
       (make-slippery-chicken
        '+ss-chords-piece-1+
        :title "ss chords piece 1"
        :instrument-palette +slippery-chicken-standard-instrument-palette+
        :ensemble '(((pno (piano :midi-channel 1))))
        :tempo-map '((1 (q 60)))
        :set-palette '((1 ((f3 g3 a3 bf3 c4 d4 e4 f4 g4 a4 b4 c5 d5 e5 f5 g5)
                           :subsets ((piano-chord (c4 e4 g4))))))
        :set-map '((1 (1 1 1 1 1 1)))
        :rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                                :pitch-seq-palette ((8 (7) 8 7 (8) 7 8 7)))))
        :rthm-seq-map '((1 ((pno (1 1 1 1 1 1))))))))
  (write-lp-data-for-all ss-chords-piece-1 :base-path "/tmp/"))
chords-ss-fun-1.png

However, the above code requires that there is a subset named piano-chord in every set from which chords are made for the piano. If slippery chicken attempts to make a chord from the piano-chord subset and that subset is not present in the current set, it will exit with an error. A second function could be used as a safeguard against this:

(defun piano-chord-master (curve-num index pitch-list pitch-seq instrument set)
  (let* ((xss (piano-subset-fun curve-num index pitch-list pitch-seq instrument
                        set)))
    (if xss
        xss
        (chord-fun2 curve-num index pitch-list pitch-seq instrument set))))

This function declares the variable xss to be the result of the piano-subset-fun function. If the current set has a subset named piano-chord, the result of the piano-subset-fun will be a chord; if not, the result will be NIL. If the result is a chord, the piano-chord-master uses that chord. If the result is NIL, the predefined chord-fun2 function will be used instead.

This is now the chord-function that the piano instrument must use, and must be assigned correspondingly:

(set-slot 'chord-function 'piano-chord-master 'piano 
          +slippery-chicken-standard-instrument-palette+)

Now whenever slippery chicken encounters a chord indication for the piano instrument, it will first try to make the chord from the pitches in the subset of that set with the ID piano-chord. If no such subset exists, it will make a chord using chord-fun2:

(let* ((ss-chords-piece-2
       (make-slippery-chicken
        '+ss-chords-piece-2+
        :title "ss chords piece 2"
        :instrument-palette +slippery-chicken-standard-instrument-palette+
        :ensemble '(((pno (piano :midi-channel 1))))
        :tempo-map '((1 (q 60)))
        :set-palette '((1 ((f3 g3 a3 bf3 c4 d4 e4 f4 g4 a4 b4 c5 d5 e5 f5 g5)
                           :subsets ((piano-chord (c4 e4 g4)))))
                       (2 ((fs3 gs3 as3 b3 cs4 ds4 e4 fs4 gs4 as4 b4 cs5 ds5 e5
                                fs5 gs5))))
        :set-map '((1 (1 2 1 2 1 2)))
        :rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                                :pitch-seq-palette ((8 (7) 8 7 (8) 7 8 7)))))
        :rthm-seq-map '((1 ((pno (1 1 1 1 1 1))))))))
  (write-lp-data-for-all ss-chords-piece-2 :base-path "/tmp/"))
chords-ss-fun-2.png

close

+ Example 2—Cycling through many subsets of the same set

The user may also want to have several chords available to an instrument when a specific set is used. This could be done using nested subsets (see the page on pitches for more detail) and getting chords from the subset based on specific or random IDs.

In addition to getting items from a set randomly or by using specific IDs, slippery chicken also has a built-in cycling feature for many of its classes. The software will remember the last item used for some lists, allowing the user to always get the next item from that list every time the list is accessed. That approach is employed in this second example.

Using this feature makes the corresponding chord selection function much sleeker:

(defun piano-ss-fun2 (curve-num index pitch-list pitch-seq instrument set) 
 (declare (ignore curve-num index pitch-list pitch-seq instrument))
 (make-chord (data (get-next (get-data-data 'piano-chords (subsets set))))))

As with the first example, this function also only makes use of the set argument (and, like the first example, will also overlook any values assigned to set-limits-high and -low). It gets the data of the data associated with the id piano-chords in the subsets slot of the current set. This data is an object containing pitches. Each time the function is employed, it gets the next item in the piano-chords subset. It then makes a chord from the data (pitches) of that item.

However, the get-next method will only work if it is used with one of the classes that allow cycling, and will cause slippery chicken to exit with an error otherwise. A nested subset would be the right kind of list, but if not all of the sets in the piece have a nested subset, an additional safeguard must be programmed to ensure that the piano-ss-fun2 function is only used when the current set contains one. This safeguard can be similar to the safeguard from the first example, in that it runs a test, and if the test fails, it uses one of the predefined chord functions instead.

(defun piano-mc-2 (curve-num index pitch-list pitch-seq instrument set)
  (let* ((ss (when (subsets set) (get-data 'piano-chords (subsets set) nil)))
         (sss (when ss (data ss))))
    (if (is-ral sss)
        (piano-ss-fun2 curve-num index pitch-list pitch-seq instrument set)
        (piano-chord-fun curve-num index pitch-list pitch-seq instrument set))))

This function first declares the variable ss and assigns to it the data associated with the piano-chords ID in the subsets slot of the current set. If there is no subset with that ID, ss will automatically be given the value NIL. If there is such a subset, the variable sss is assigned to the data of that subset, which may be a list of pitches or a nested subset. The function then uses the is-ral function to test whether that data is a nested subset (a recursive association list, or ral). If it is a recursive-assoc-list, the function uses the piano-ss-fun2 to get the next list of pitches from that ral and make the chord, otherwise it uses the predefined piano-chord-fun.

This function is assigned to the chord-function slot of the piano instrument the instrument-palette being used in the piece:

(set-slot 'chord-function 'piano-mc-2 'piano 
          +slippery-chicken-standard-instrument-palette+)

Now whenever slippery chicken encounters a chord indication for the piano, it will first try to get the next nested subset from the subset of the current set with the ID piano-chords and make a chord from those pitches. If no such subset exists, it will make a chord using piano-chord-fun instead:

(let* ((subset-piece-3
       (make-slippery-chicken
        '+subset-piece-3+
        :title "subset piece 3"
        :instrument-palette +slippery-chicken-standard-instrument-palette+
        :ensemble '(((pno (piano :midi-channel 1))))
        :tempo-map '((1 (q 60)))
        :set-palette '((1 ((f3 g3 a3 bf3 c4 d4 e4 f4 g4 a4 b4 c5 d5 e5 f5 g5)
                           :subsets ((piano-chords ((pno1 (c4 e4 g4))
                                                    (pno2 (d4 f4 a4))
                                                    (pno3 (e4 g4 b4)))))))
                       (2 ((fs3 gs3 as3 b3 cs4 ds4 e4 fs4 gs4 as4 b4 cs5))))   
        :set-map '((1 (1 2 1 2 1 2)))
        :rthm-seq-palette '((1 ((((4 4) - e e e e - - e e e e -))
                                :pitch-seq-palette ((8 (7) 8 7 (8) 7 8 7)))))
        :rthm-seq-map '((1 ((pno (1 1 1 1 1 1))))))))
  (write-lp-data-for-all subset-piece-3 :base-path "/tmp/"))
chords-ss-fun-3.png

close