Tonal composition with slippery chicken
+ Associated example files
slippery chicken was not explicitly designed to produce tonal music. However, with a bit of planning and some careful programming, the software can be used to algorithmically generate tonal compositions as well.
The approach used in this demo composition defines 17 fixed,
polyphonic fragments that can be transposed, interchanged, and
interconnected in a kind of modular composition technique. These
modules are then assembled algorithmically using a repeatable
pseudo-random function together with
the get-linear-sequence
method of
the l-for-lookup
class.
+ The modules: 3- and 4-voiced improvisation patterns
The modules that serve as the building blocks for this piece are based on an approach to classical keyboard improvisation and voice-oriented (rather than harmony-based) musical analysis taught to by Volkhardt Preuss at the Hochschule für Musik und Theater in Hamburg, Germany.
This method collects fundamental musical structures into five basic categories: Fauxbourdon (parallel triads in first inversion, often with 7-6 suspensions), Ruggiero (Dominant-Tonic cadences), Sekundakkord (Dominant-seventh chords with the 7 in the bass), Orgelpunkt (pedal-point sequences over a sustained bass), and Terzfall (falling third sequences). The first four of these have been implemented in this piece.
In this categorical approach, the five pattern types are first reduced and presented as core sequences, in which all of the voices progress in simultaneous half-note motion. This half-note basis can then be extended and elaborated (possibly over several measures) to create more fluid music. The demo composition in this tutorial sticks to just the short, core progressions with half-note motion.
+ sc's pitch-selection algorithm and voice-leading
slippery chicken's automatic pitch-selection algorithm was designed to produce atonal music, and it needs the use of some creative constraints to get it to produce tonal melodies with traditional voice-leading.
Because slippery chicken uses the same pitch set for all
players in any given sequence, the approach taken here is to confine
each set to different subsets for each player in each sequence using
the set-limits-high
and set-limits-low
slots, thereby enabling an exact mapping of the numbers in
each pitch-seq
to specific pitches of the current
set.
The code for this composition applies this technique once for
each rthm-seq
, and then transposes the pitch sets using
the transposition
slot of the set-palette
object, thereby ensuring that the same scale degrees are selected for
the pitches of each rthm-seq
when
that rthm-seq
is repeated in conjunction with different
sets.
+ Converting note-names to pitch-seq numbers
A small function has been written for this piece that makes it
easier to determine the numbers required for the
tonal pitch-seqs
. It takes as its two arguments a list
of note-name symbols that are to be converted
into pitch-seq
numbers, and the "source" list of
note-name symbols to which the pitch-seq
numbers are to
be applied.
This function allows the user to enter note-name symbols directly,
which are then automatically converted into pitch-seq
numbers, rather than counting through all the pitches in the set or
subset to determine which numbers are needed in order to produce
them. The function requires that
the :avoid-melodic-octaves
and :avoid-used-notes
arguments of
the make-slippery-chicken
function are both set
to NIL
in order to work properly.
Every pitch in the first list must be present in the "source" list
(second argument) to prevent errors. Enharmonically equivalent
pitches are not considered equal; the note-name symbols must be exact
(i.e. GS4
must be written as GS4
and
not AF4
). The function then determines the
resulting pitch-seq
numbers by returning
the position (index number) of each specified pitch in the
source set and subtracting the required offset so that the lowest
pitch is always 1.
(defun pitches-to-pseq (pitch-list set) (let* (positions lowest pseq) (setf positions (mapcar #'(lambda (x) (position x set)) pitch-list)) (setf lowest (1- (first (sort (copy-list positions) #'<)))) (setf pseq (mapcar #'(lambda (x) (- x lowest)) positions)) pseq))
SC> (pitches-to-pseq '(c4 e4 g4 f4 d4 e4 c4) '(a3 b3 c4 d4 e4 f4 g4 a4)) (1 3 5 4 2 3 1)
+ Generating the set-palette with :transposition
Core sets
The code for this piece uses two primary sets: one that is
predominantly major, with chromatic passing pitches, and one that is
predominantly minor, also with chromatic passing pitches. These are
defined at the top of the variable declarations within
the let*
enclosure. Since
the pitches-to-pseq
function requires exact note-name
symbols, the sets contain both enharmonic spellings of some of the
pitches, such as fs4
and gf4
.
(core-set-maj '(g2 a2 bf2 b2 c3 d3 ef3 e3 f3 g3 a3 b3 c4 cs4 d4 ef4 e4 f4 fs4 gf4 g4 gs4 af4 a4 bf4 b4 c5 d5 e5 f5 fs5 g5 b5 c6)) (core-set-min '(g2 af2 bf2 c3 ef3 f3 fs3 g3 af3 bf3 b3 c4 d4 ef4 e4 f4 fs4 gf4 g4 af4 a4 bf4 b4 c5 ef5 e5 f5))
Using a loop to create the set-palette
The following loop is used to create the set-palette
and assign it to the variable sp
:
(sp (loop for i from 0 to 11 if (= i 0) collect (list "0-MAJ" (list core-set-maj)) and collect (list "0-MIN" (list core-set-min)) else collect (list (format nil "~a-MAJ" i) (list "0-MAJ" ':transposition i)) and collect (list (format nil "~a-MIN" i) (list "0-MIN" ':transposition i))))
The :transposition slot
The set-palette
is assembled automatically in the loop
by collecting 12 transpositions (from 0
to 11
semitones) of each of the core-set-
variables declared above. The integer value passed to
the :transposition
slot of a set within
a set-palette
object will automatically transpose the
source set by that many semitones.
Assembling the set-palette
in this way means there will
be one major and one minor set for each of the 12 chromatic
pitch-classes (0
= C, 1
=
C-sharp, 2
= D, etc.). Modulating to a different key
within the piece, then, is made possible by selecting the
corresponding set in the set-map
.
Using strings as IDs
The IDs for the individual sets of this set-palette
are
strings, such as "0-MAJ"
or "0-MIN"
, rather
than the numbers or symbols used in the other tutorials. This allows
for easier concatenation of numbers and symbols when automatically
assembling the set-palette
within the loop
macro.
The same number used for the semitone transposition is combined with
either "-MAJ"
or "-MIN"
to create the ID
for the given set.
The resulting set-palette
would thus look like this,
if written out in full:
(("0-MAJ" ((G2 A2 BF2 B2 C3 D3 EF3 E3 F3 G3 A3 B3 C4 CS4 D4 EF4 E4 F4 FS4 GF4 G4 GS4 AF4 A4 BF4 B4 C5 D5 E5 F5 FS5 G5 B5 C6))) ("0-MIN" ((G2 AF2 BF2 C3 EF3 F3 FS3 G3 AF3 BF3 B3 C4 D4 EF4 E4 F4 FS4 GF4 G4 AF4 A4 BF4 B4 C5 EF5 E5 F5))) ("1-MAJ" ("0-MAJ" :TRANSPOSITION 1)) ("1-MIN" ("0-MIN" :TRANSPOSITION 1)) ("2-MAJ" ("0-MAJ" :TRANSPOSITION 2)) ("2-MIN" ("0-MIN" :TRANSPOSITION 2)) ("3-MAJ" ("0-MAJ" :TRANSPOSITION 3)) ("3-MIN" ("0-MIN" :TRANSPOSITION 3)) ("4-MAJ" ("0-MAJ" :TRANSPOSITION 4)) ("4-MIN" ("0-MIN" :TRANSPOSITION 4)) ("5-MAJ" ("0-MAJ" :TRANSPOSITION 5)) ("5-MIN" ("0-MIN" :TRANSPOSITION 5)) ("6-MAJ" ("0-MAJ" :TRANSPOSITION 6)) ("6-MIN" ("0-MIN" :TRANSPOSITION 6)) ("7-MAJ" ("0-MAJ" :TRANSPOSITION 7)) ("7-MIN" ("0-MIN" :TRANSPOSITION 7)) ("8-MAJ" ("0-MAJ" :TRANSPOSITION 8)) ("8-MIN" ("0-MIN" :TRANSPOSITION 8)) ("9-MAJ" ("0-MAJ" :TRANSPOSITION 9)) ("9-MIN" ("0-MIN" :TRANSPOSITION 9)) ("10-MAJ" ("0-MAJ" :TRANSPOSITION 10)) ("10-MIN" ("0-MIN" :TRANSPOSITION 10)) ("11-MAJ" ("0-MAJ" :TRANSPOSITION 11)) ("11-MIN" ("0-MIN" :TRANSPOSITION 11)))
+ Using the l-for-lookup class for harmonic progressions
The l-for-lookup
class has a method called
get-linear-sequence
which lends itself to the generation
of harmonic progressions. Instead of using the rules of a
standard l-for-lookup
(Lindenmayer System) object to
create its resulting list by substitution (or rewriting), it uses
them in rotation.
Each time a particular rule-data pair is accessed, the method selects the next item in the data list associated with the given rule. When it has used the last item in a data list, it cycles around and begins at the top of the list the next time that rule-data pair is accessed.
The l-for-lookup
object and call
to get-linear-sequence
in this simple example here
illustrate this method:
(let ((lfl (make-l-for-lookup 'lfl nil '((1 (2 3)) (2 (1)) (3 (1 2)))))) (get-linear-sequence lfl 1 30)) => (1 2 1 3 1 2 1 3 2 1 2 1 3 1 2 1 3 2 1 2 1 3 1 2 1 3 2 1 2 1)
The data
slot of the l-for-lookup
object
can be set to NIL
when used
with get-linear-sequence
, since this method does not use
that slot. The call to get-linear-sequence
states the
name of the object to access, the first ID to use as a look-up key
(a.k.a. the "seed" or "axiom"; 1
in this example), and
the length of the list to return (30
here).
The method then gets the first item in the rules list associated with
the seed argument, which in this example is 2
, and uses
that item as the look-up ID to get the next item. The only item in
the list associated with rule 2
in this example
is 1
. The number 1
is then used again here
as the look-up ID, but this time it gets the next item from the
associated data list: 3
. Now that all items associated
with rule 1
have been used, the next time 1
is used as a look-up ID, it will select the first item of the list
again, namely 2
, and so on.
The procedure for this piece
The procedure used to generate the sequence of modules in this piece
(i.e. the rthm-seq-map
) function similarly. Instead of
using numbers, however, the keys consist of symbols that identify the
different module types, and the associated data consist of symbols
that identify the module types to which each given module type may
progress. The key-data items of this initial object, and the
resulting list, are used later as keys again to look up data from two
further association lists, namely transposition-rules-al
and combos-al
.
The symbol names for each of the module types here are created from
their kind, their variation, and their modality (major, minor,
starting major but ending minor, etc.). They each
represent groups of modules, as defined in
the combos-al
variable below. Each group may have one or
more possible combination of voices (e.g., the cantizans may
appear in the soprano voice in one combination, but in the alto voice
in another, etc.)
(seq-rules (make-l-for-lookup 'seq-rules nil '((fauxbourdon-a-maj (ruggiero-a-maj ruggiero-b-maj ruggiero-b-maj-to-min ruggiero-c-maj)) (fauxbourdon-a-min (ruggiero-a-min ruggiero-d-min-to-maj)) (ruggiero-a-maj (fauxbourdon-a-maj sekund-a-maj ped-a-maj)) (ruggiero-a-min (fauxbourdon-a-min sekund-a-min)) (ruggiero-b-maj (fauxbourdon-a-maj sekund-a-maj ped-a-maj)) (ruggiero-b-maj-to-min (fauxbourdon-a-min sekund-a-min)) (ruggiero-c-maj (fauxbourdon-a-maj sekund-a-maj ped-a-maj)) (ruggiero-d-min-to-maj (fauxbourdon-a-maj sekund-a-maj ped-a-maj)) (ruggiero-e-maj (fauxbourdon-a-maj sekund-a-maj ped-a-maj)) (sekund-a-maj (ruggiero-e-maj)) (sekund-a-min (ruggiero-a-min ruggiero-d-min-to-maj)) (ped-a-maj (sekund-a-maj)))))
Getting the linear sequence
The sequence of modules is then created by calling
the get-linear-sequence
method with the
symbol fauxbourdon-a-maj
as the seed, and a list length
of num-seqs
, which is the first variable declared in
this let*
enclosure (29
here). The
resulting list is stored in the variable seq-seq
.
(seq-seq (get-linear-sequence seq-rules 'fauxbourdon-a-maj num-seqs))
The resulting sequence of modules in this instance is:
(FAUXBOURDON-A-MAJ RUGGIERO-A-MAJ FAUXBOURDON-A-MAJ RUGGIERO-B-MAJ FAUXBOURDON-A-MAJ RUGGIERO-B-MAJ-TO-MIN FAUXBOURDON-A-MIN RUGGIERO-A-MIN FAUXBOURDON-A-MIN RUGGIERO-D-MIN-TO-MAJ FAUXBOURDON-A-MAJ RUGGIERO-C-MAJ FAUXBOURDON-A-MAJ RUGGIERO-A-MAJ SEKUND-A-MAJ RUGGIERO-E-MAJ FAUXBOURDON-A-MAJ RUGGIERO-B-MAJ SEKUND-A-MAJ RUGGIERO-E-MAJ SEKUND-A-MAJ RUGGIERO-E-MAJ PED-A-MAJ SEKUND-A-MAJ RUGGIERO-E-MAJ FAUXBOURDON-A-MAJ RUGGIERO-B-MAJ-TO-MIN SEKUND-A-MIN RUGGIERO-A-MIN)
Adding a coda
This particular sequence, created using a list length
of 29
, generates a sequence that ends with a V-I cadence
(a ruggiero module). However, not all list lengths will
generate lists with a ruggiero as their last element; a length
of 30
, for example, would generate a sequence that ends
with SEKUND-A-MIN
, and the piece would sound like it
gets cut off in the middle of a phrase.
To remedy this, a small loop is used to generate a coda if
necessary. The loop looks at the last item in
the seq-seq
list, and if it does not start with the
characters RUG
, it collects a new linear sequence of
module symbol names, using the same rules as above, until
a ruggiero is reached. It saves this new short list to the
variable coda
, which is then appended to the end of
the seq-seq
. The sequence is now complete and will end
with a V-I cadence.
(coda (loop with l = (first (last seq-seq)) until (equalp (subseq (format nil "~a" l) 0 3) "RUG") do (setf l (first (last (get-linear-sequence seq-rules l 2)))) collect l)) (seq-seq (append seq-seq coda))
+ An assoc-list object to store transposition rules
A number of the modules selected for this
particular slippery-chicken
object modulate to different
keys and different modes (major or
minor). The ruggiero-b-maj-to-min
module, for example,
starts on the V (Dominant) of a given major key and uses a
musical sequence to modulate to the minor key three
half-steps higher (e.g. from C major to E-flat minor).
Each consecutive set chosen for the set-map
is
therefore dependent on whether the previous module modulates or
not. Accordingly, then, a list of transposition rules are first
collected into an association list (an assoc-list
object), and each of the symbol names defined for each module in
the l-for-lookup
object above are once again used as
keys to store lists of two items: a positive or negative integer to
determine the number of half-steps by which the piece is to modulate
at the end of each module, and a symbol to indicate whether the new
key is to be major or minor. This assoc-list
object is
stored in the variable transposition-rules-al
.
(transposition-rules-al (make-assoc-list 'transposition-rules '((fauxbourdon-a-maj (0 maj)) (fauxbourdon-a-min (0 min)) (ruggiero-a-maj (0 maj)) (ruggiero-a-min (0 min)) (ruggiero-b-maj (3 maj)) (ruggiero-b-maj-to-min (3 min)) (ruggiero-c-maj (-3 maj)) (ruggiero-d-min-to-maj (0 maj)) (ruggiero-e-maj (0 maj)) (sekund-a-maj (0 maj)) (sekund-a-min (0 min)) (ped-a-maj (0 maj)))))
A small loop is then used to collect the key-change progression by
calculating the transpositions associated with each of the modules
listed in the seq-seq
variable created above. To this
end, each item in the seq-seq
is consecutively used as
the key to perform a look-up into
the transposition-rules-al
association list.
(transpositions-changes (let ((n 0) rule) (loop for seq in (subseq seq-seq 0 (1- (length seq-seq))) do (setf rule (get-data-data seq transposition-rules-al)) collect (list (setf n (mod (+ n (first rule)) 12)) (second rule)))))
The resulting list, which is stored in the
variable transpositions-changes
, consists of a series of
two-item lists, each of which contain the number and symbol
(MAJ
or MIN
) required to create the string
ID of one of the sets in the set-palette
. The loop
therefore performs a cumulative summation of the corresponding
transposition integers from the transposition-rules-al
in modulo 12 to ensure that the transposition always returns
a number between 0
and 11
, the first part
of an existing set ID. The results for this particular piece (with 29
initial sequences) is:
((0 MAJ) (0 MAJ) (0 MAJ) (0 MAJ) (3 MAJ) (3 MAJ) (6 MIN) (6 MIN) (6 MIN) (6 MIN) (6 MAJ) (6 MAJ) (3 MAJ) (3 MAJ) (3 MAJ) (3 MAJ) (3 MAJ) (3 MAJ) (6 MAJ) (6 MAJ) (6 MAJ) (6 MAJ) (6 MAJ) (6 MAJ) (6 MAJ) (6 MAJ) (6 MAJ) (9 MIN) (9 MIN))
However, this list is just a list of the key changes that occur at
the end of each module. In order to have one key
for each module, the next
variable, transpositions
, pushes (0 MAJ)
to
the head of this list. There is now one key and one mode stored for
each item in the seq-seq
(transpositions (push '(0 MAJ) transpositions-changes))
+ Assembling the set-map from the transpositions list
The set-map
is then created from
the transpositions
variable. A small loop collects a
list of strings constructed by concatenating the integer from each
two-item list with the corresponding symbol.
(sm (loop for trnsp in transpositions collect (format nil "~a-~a" (first trnsp) (second trnsp))))
Each of these strings will be equal to the ID of one of the
transposed sets in the set-palette
. The resulting list
is stored in the variable sm
, which is passed directly
to the set-map
slot of the slippery-chicken
object made below. The set-map
for this particular
sequence of 29 modules looks like this:
("0-MAJ" "0-MAJ" "0-MAJ" "0-MAJ" "3-MAJ" "3-MAJ" "6-MIN" "6-MIN" "6-MIN" "6-MIN" "6-MAJ" "6-MAJ" "3-MAJ" "3-MAJ" "3-MAJ" "3-MAJ" "3-MAJ" "3-MAJ" "6-MAJ" "6-MAJ" "6-MAJ" "6-MAJ" "6-MAJ" "6-MAJ" "6-MAJ" "6-MAJ" "6-MAJ" "9-MIN" "9-MIN")
+ An assoc-list object to store module combinations
As mentioned above, each module type named in
the l-for-lookup
object and the
subsequent seq-seq
variable represents a group of
possible voice combinations that have the same pitch, rhythm, and
harmonic structure. These combinations are defined in
another assoc-list
object here and assigned to the
variable combos-al
:
(combos-al (make-assoc-list 'combos '((fauxbourdon-a-maj ((fx-1-maj-c fx-1-maj-a fx-1-maj-t 2-rest) (fx-1-maj-c fx-1-maj-a 2-rest fx-2-maj-t) (fx-3-maj-c fx-1-maj-c 2-rest fx-2-maj-t))) (fauxbourdon-a-min ((fx-1-min-c fx-1-min-a fx-1-min-t 2-rest) (fx-1-min-c fx-1-min-a 2-rest fx-2-min-t))) (ruggiero-a-maj ((rg-1-maj-c rg-1-maj-a rg-1-maj-t 2-rest))) (ruggiero-a-min ((rg-1-min-c rg-1-min-a rg-1-min-t 2-rest) (rg-4-min-c rg-4-min-a 2-rest rg-4-min-b))) (ruggiero-b-maj ((rg-2-maj-c rg-2-maj-a 5-rest rg-2-maj-t))) (ruggiero-b-maj-to-min ((rg-2-min-c rg-2-min-a 5-rest rg-2-min-t))) (ruggiero-c-maj ((rg-3-maj-c rg-3-maj-a rg-3-maj-t rg-3-maj-b))) (ruggiero-d-min-to-maj ((rg-5-min-c rg-5-min-a rg-5-min-t rg-5-min-b))) (ruggiero-e-maj ((rg-6-maj-c rg-6-maj-a 2-rest rg-6-maj-b))) (sekund-a-maj ((sek-1-maj-c sek-1-maj-a sek-1-maj-t sek-1-maj-b) (sek-2-maj-c sek-2-maj-a sek-2-maj-t sek-2-maj-b))) (sekund-a-min ((sek-3-min-c sek-3-min-a sek-3-min-t sek-3-min-b))) (ped-a-maj ((ped-1-maj-c ped-1-maj-a ped-1-maj-t ped-1-maj-b))))))
Each key in this association list is exactly the same as the keys
in the l-for-lookup
object and
the transposition-rules-al
. The data lists associated
with each key consist of the IDs of rthm-seq
objects
defined in the rthm-seq-palette
below.
Each rthm-seq
below consists of one voice within one of
the modules. Each data item in the combos-al
is
therefore a set of four rthm-seq
IDs, one for each
player. These are first collected here in combination to make the
identification easier for the programmer. They will then be
disassembled and reassembled into the rthm-seq-map
in
the next two steps below.
+ Assembling the rthm-seq-map from the combos-al
Collecting the random combos first
A small loop is first used to collect the list
of rthm-seq
combinations for the
subsequent rthm-seq-map
. It passes through the list of
module types stored in the seq-seq
variable and uses
each one as a look-up key into the combos-al
. It uses
slippery chicken's own random-rep
function to
select a random voice combo from the data list associated with each
module type.
(rsm-combos (let* (c) (random-rep 1 t) ; reset random state each time for same results (loop for s in seq-seq do (setf c (get-data-data s combos-al)) collect (nth (random-rep (length c)) c))))
The random-rep
function has an optional argument that
allows the user to reset the current Lisp's random state, allowing
for the exact same list of randomly selected items to be returned
each time. In this example, random-rep
is called first,
prior to the loop, so that the random state is reset. The result of
this loop is stored in the rsm-combos
variable and
produces the following list for this example:
Update 19.6.13: The random-rep function has been replaced with a hard-coded list in tonal-algo-example.lsp so that the enharmonic corrections done 'by hand' still function when the file is run in any version of Lisp.
((FX-1-MAJ-C FX-1-MAJ-A FX-1-MAJ-T 2-REST) (RG-1-MAJ-C RG-1-MAJ-A RG-1-MAJ-T 2-REST) (FX-1-MAJ-C FX-1-MAJ-A FX-1-MAJ-T 2-REST) (RG-2-MAJ-C RG-2-MAJ-A 5-REST RG-2-MAJ-T) (FX-1-MAJ-C FX-1-MAJ-A FX-1-MAJ-T 2-REST) (RG-2-MIN-C RG-2-MIN-A 5-REST RG-2-MIN-T) (FX-1-MIN-C FX-1-MIN-A 2-REST FX-2-MIN-T) (RG-4-MIN-C RG-4-MIN-A 2-REST RG-4-MIN-B) (FX-1-MIN-C FX-1-MIN-A 2-REST FX-2-MIN-T) (RG-5-MIN-C RG-5-MIN-A RG-5-MIN-T RG-5-MIN-B) (FX-1-MAJ-C FX-1-MAJ-A FX-1-MAJ-T 2-REST) (RG-3-MAJ-C RG-3-MAJ-A RG-3-MAJ-T RG-3-MAJ-B) (FX-1-MAJ-C FX-1-MAJ-A FX-1-MAJ-T 2-REST) (RG-1-MAJ-C RG-1-MAJ-A RG-1-MAJ-T 2-REST) (SEK-2-MAJ-C SEK-2-MAJ-A SEK-2-MAJ-T SEK-2-MAJ-B) (RG-6-MAJ-C RG-6-MAJ-A 2-REST RG-6-MAJ-B) (FX-3-MAJ-C FX-1-MAJ-C 2-REST FX-2-MAJ-T) (RG-2-MAJ-C RG-2-MAJ-A 5-REST RG-2-MAJ-T) (SEK-2-MAJ-C SEK-2-MAJ-A SEK-2-MAJ-T SEK-2-MAJ-B) (RG-6-MAJ-C RG-6-MAJ-A 2-REST RG-6-MAJ-B) (SEK-1-MAJ-C SEK-1-MAJ-A SEK-1-MAJ-T SEK-1-MAJ-B) (RG-6-MAJ-C RG-6-MAJ-A 2-REST RG-6-MAJ-B) (PED-1-MAJ-C PED-1-MAJ-A PED-1-MAJ-T PED-1-MAJ-B) (SEK-2-MAJ-C SEK-2-MAJ-A SEK-2-MAJ-T SEK-2-MAJ-B) (RG-6-MAJ-C RG-6-MAJ-A 2-REST RG-6-MAJ-B) (FX-1-MAJ-C FX-1-MAJ-A FX-1-MAJ-T 2-REST) (RG-2-MIN-C RG-2-MIN-A 5-REST RG-2-MIN-T) (SEK-3-MIN-C SEK-3-MIN-A SEK-3-MIN-T SEK-3-MIN-B) (RG-1-MIN-C RG-1-MIN-A RG-1-MIN-T 2-REST))
Separating the list of combos into a list for the rthm-seq-map
The next little loop then parses the rsm-combos
list
into separate lists for each player, with the syntax necessary to
make a rthm-seq-map
object, and stores the resulting
list in the variable rsm
. This variable will then be
used directly within the call
to make-slippery-chicken
.
(rsm (loop for p in '(vo vt va vc) for n from 0 collect (list p (flatten (loop for c in rsm-combos collect (list (nth n c)))))))
The result of this loop for this example is as follows:
((VO (FX-1-MAJ-C RG-1-MAJ-C FX-1-MAJ-C RG-2-MAJ-C FX-1-MAJ-C RG-2-MIN-C FX-1-MIN-C RG-4-MIN-C FX-1-MIN-C RG-5-MIN-C FX-1-MAJ-C RG-3-MAJ-C FX-1-MAJ-C RG-1-MAJ-C SEK-2-MAJ-C RG-6-MAJ-C FX-3-MAJ-C RG-2-MAJ-C SEK-2-MAJ-C RG-6-MAJ-C SEK-1-MAJ-C RG-6-MAJ-C PED-1-MAJ-C SEK-2-MAJ-C RG-6-MAJ-C FX-1-MAJ-C RG-2-MIN-C SEK-3-MIN-C RG-1-MIN-C)) (VT (FX-1-MAJ-A RG-1-MAJ-A FX-1-MAJ-A RG-2-MAJ-A FX-1-MAJ-A RG-2-MIN-A FX-1-MIN-A RG-4-MIN-A FX-1-MIN-A RG-5-MIN-A FX-1-MAJ-A RG-3-MAJ-A FX-1-MAJ-A RG-1-MAJ-A SEK-2-MAJ-A RG-6-MAJ-A FX-1-MAJ-C RG-2-MAJ-A SEK-2-MAJ-A RG-6-MAJ-A SEK-1-MAJ-A RG-6-MAJ-A PED-1-MAJ-A SEK-2-MAJ-A RG-6-MAJ-A FX-1-MAJ-A RG-2-MIN-A SEK-3-MIN-A RG-1-MIN-A)) (VA (FX-1-MAJ-T RG-1-MAJ-T FX-1-MAJ-T 5-REST FX-1-MAJ-T 5-REST 2-REST 2-REST 2-REST RG-5-MIN-T FX-1-MAJ-T RG-3-MAJ-T FX-1-MAJ-T RG-1-MAJ-T SEK-2-MAJ-T 2-REST 2-REST 5-REST SEK-2-MAJ-T 2-REST SEK-1-MAJ-T 2-REST PED-1-MAJ-T SEK-2-MAJ-T 2-REST FX-1-MAJ-T 5-REST SEK-3-MIN-T RG-1-MIN-T)) (VC (2-REST 2-REST 2-REST RG-2-MAJ-T 2-REST RG-2-MIN-T FX-2-MIN-T RG-4-MIN-B FX-2-MIN-T RG-5-MIN-B 2-REST RG-3-MAJ-B 2-REST 2-REST SEK-2-MAJ-B RG-6-MAJ-B FX-2-MAJ-T RG-2-MAJ-T SEK-2-MAJ-B RG-6-MAJ-B SEK-1-MAJ-B RG-6-MAJ-B PED-1-MAJ-B SEK-2-MAJ-B RG-6-MAJ-B 2-REST RG-2-MIN-T SEK-3-MIN-B 2-REST)))
+ set-limits for the C-transpositions of the rthm-seqs
The next three variables form the core of this modular approach to
creating tonal music with slippery chicken, and they involve
the set-limits-high
and set-limits-low
slots of the slippery-chicken
object.
By constraining the current set for each player in every sequence of
the piece and setting the :avoid-melodic-octaves
and :avoid-used-notes
keyword arguments of
the make-slippery-chicken
function to NIL
,
the user can enable an exact mapping of the pitch-seq
numbers of the rthm-seq
objects to specific pitches in
the current set.
Defining the pitch limits and storing them in an assoc-list
This demo piece does this by first creating
an assoc-list
object that stores the lowest and highest
pitch of each rthm-seq
in its C (0) transposition. Each
key in this association list (which is assigned to the
variable lims-al
) is identical to the ID of one of
the rthm-seqs
defined in
the rthm-seq-palette
below.
(lims-al (make-assoc-list 'set-lims '((2-rest (a4 a4)) (5-rest (a4 a4)) (fx-1-maj-c (fs4 a4)) (fx-1-maj-a (b3 e4)) (fx-1-maj-t (g3 c4)) (fx-1-min-c (fs4 af4)) (fx-1-min-a (b3 ef4)) (fx-1-min-t (g3 c4)) (fx-2-maj-t (g2 c3)) (fx-2-min-t (g2 c3)) (fx-3-maj-c (b4 e5)) (rg-1-maj-c (b4 c5)) (rg-1-maj-a (c4 g4)) (rg-1-maj-t (c3 g3)) (rg-1-min-c (b4 c5)) (rg-1-min-a (c4 g4)) (rg-1-min-t (c3 g3)) (rg-2-maj-c (ef4 c5)) (rg-2-maj-a (c4 f4)) (rg-2-maj-t (bf2 g3)) (rg-2-min-c (ef4 c5)) (rg-2-min-a (c4 f4)) (rg-2-min-t (bf2 g3)) (rg-3-maj-c (gs4 c5)) (rg-3-maj-a (e4 g4)) (rg-3-maj-t (b3 e4)) (rg-3-maj-b (a2 a3)) (rg-4-min-c (b4 c5)) (rg-4-min-a (c4 g4)) (rg-4-min-b (g2 g3)) (rg-5-min-c (b4 c5)) (rg-5-min-a (e4 g4)) (rg-5-min-t (c4 d4)) (rg-5-min-b (g2 af3)) (rg-6-maj-c (c5 g5)) (rg-6-maj-a (b4 c5)) (rg-6-maj-b (c3 g3)) (sek-1-maj-c (e5 f5)) (sek-1-maj-a (c5 c5)) (sek-1-maj-t (f4 g4)) (sek-1-maj-b (a2 c3)) (sek-2-maj-c (e5 g5)) (sek-2-maj-a (c5 d5)) (sek-2-maj-t (g4 a4)) (sek-2-maj-b (b2 c3)) (sek-3-min-c (ef5 f5)) (sek-3-min-a (c5 c5)) (sek-3-min-t (f4 g4)) (sek-3-min-b (af2 c3)) (ped-1-maj-c (c5 c5)) (ped-1-maj-a (g4 a4)) (ped-1-maj-t (e4 f4)) (ped-1-maj-b (c3 c3)))))
Creating the set-limits- lists
A small loop is then used to collect just the pitches, in MIDI-note
number form, for the set-limits-low
and set-limits-high
slots for every sequence and every
player in the piece. It collects them as MIDI-note numbers in order
to facilitate their transposition in the next step.
(lims-lo-hi (loop for n from 0 to 1 collect (loop for p in rsm collect (loop for rs in (second p) collect (note-to-midi (nth n (get-data-data rs lims-al)))))))
The list created consists of two lists, one for the low set limits
and one for the high set limits, each of those lists in turn
containing a list of pitches for each player, with one pitch for each
sequence. This list is assigned to the
variable lims-lo-hi
. The result of this list in this
instance with 29 sequences looks like this:
(((66 71 66 63 66 63 66 71 66 71 66 68 66 71 76 72 71 63 76 72 76 72 72 76 72 66 63 75 71) (59 60 59 60 59 60 59 60 59 64 59 64 59 60 72 71 66 60 72 71 72 71 67 72 71 59 60 72 60) (55 48 55 69 55 69 69 69 69 60 55 59 55 48 67 69 69 69 67 69 65 69 64 67 69 55 69 65 48) (69 69 69 46 69 46 43 43 43 43 69 45 69 69 47 48 43 46 47 48 45 48 48 47 48 69 46 44 69)) ((69 72 69 72 69 72 68 72 68 72 69 72 69 72 79 79 76 72 79 79 77 79 72 79 79 69 72 77 72) (64 67 64 65 64 65 63 67 63 67 64 67 64 67 74 72 69 65 74 72 72 72 69 74 72 64 65 72 67) (60 55 60 69 60 69 69 69 69 62 60 64 60 55 69 69 69 69 69 69 67 69 65 69 69 60 69 67 55) (69 69 69 55 69 55 48 55 48 56 69 57 69 69 48 55 48 55 48 55 48 55 48 48 55 69 55 48 69)))
+ Transposing the set-limits- curves
Since some of the modules in this piece result in transpositions, as
addressed above, the set limits too must be transposed in order to
accurately apply to the transposed sets. This is done conveniently
using a small loop, in which the integer from each two-item sublist
of the transpositions
variable is accessed and added to
the MIDI-note numbers in the lims-lo-hi
list.
(transposed-lims-lo-hi (loop for hl in lims-lo-hi collect (loop for p in hl for pl in '(vo vt va vc) collect (list pl (loop for n in p for i in transpositions for x from 1 collect x collect (+ n (first i)))))))
This loop also adds the player ID and the sequence number for each
pitch limit to its output, ensuring that the resulting list has two
items at the top level: A properly-formatted list for both
set-limits-low
and set-limits-high
,
including player IDs and lists of x-y break-point pairs, that can be
passed directly to the set-limits-low
and -high
slots from within
the make-slippery-chicken
function below.
(((VO (1 66 2 71 3 66 4 63 5 69 6 66 7 72 8 77 9 72 10 77 11 72 12 74 13 69 14 74 15 79 16 75 17 74 18 66 19 82 20 78 21 82 22 78 23 78 24 82 25 78 26 72 27 69 28 84 29 80)) (VT (1 59 2 60 3 59 4 60 5 62 6 63 7 65 8 66 9 65 10 70 11 65 12 70 13 62 14 63 15 75 16 74 17 69 18 63 19 78 20 77 21 78 22 77 23 73 24 78 25 77 26 65 27 66 28 81 29 69)) (VA (1 55 2 48 3 55 4 69 5 58 6 72 7 75 8 75 9 75 10 66 11 61 12 65 13 58 14 51 15 70 16 72 17 72 18 72 19 73 20 75 21 71 22 75 23 70 24 73 25 75 26 61 27 75 28 74 29 57)) (VC (1 69 2 69 3 69 4 46 5 72 6 49 7 49 8 49 9 49 10 49 11 75 12 51 13 72 14 72 15 50 16 51 17 46 18 49 19 53 20 54 21 51 22 54 23 54 24 53 25 54 26 75 27 52 28 53 29 78))) ((VO (1 69 2 72 3 69 4 72 5 72 6 75 7 74 8 78 9 74 10 78 11 75 12 78 13 72 14 75 15 82 16 82 17 79 18 75 19 85 20 85 21 83 22 85 23 78 24 85 25 85 26 75 27 78 28 86 29 81)) (VT (1 64 2 67 3 64 4 65 5 67 6 68 7 69 8 73 9 69 10 73 11 70 12 73 13 67 14 70 15 77 16 75 17 72 18 68 19 80 20 78 21 78 22 78 23 75 24 80 25 78 26 70 27 71 28 81 29 76)) (VA (1 60 2 55 3 60 4 69 5 63 6 72 7 75 8 75 9 75 10 68 11 66 12 70 13 63 14 58 15 72 16 72 17 72 18 72 19 75 20 75 21 73 22 75 23 71 24 75 25 75 26 66 27 75 28 76 29 64)) (VC (1 69 2 69 3 69 4 55 5 72 6 58 7 54 8 61 9 54 10 62 11 75 12 63 13 72 14 72 15 51 16 58 17 51 18 58 19 54 20 61 21 54 22 61 23 54 24 54 25 61 26 75 27 61 28 57 29 78))))
+ Variables for repetition in the rthm-seqs
The rhythms used in the rhtm-seq
objects to create each
voice of each module are highly repetitive. In order to make the
rather long rthm-seq-palette
both more efficient and
more intelligible, a series of variables are created that not only
store the more frequent rhythm combinations but also give them more
recognizable names based on their function. In a few cases, the same
rhythm combinations are given more than one variable name, as they
serve more than one voice-leading function within the tonal modules
that follow.
The time signature (ts
) and ten "bare" (i.e., without a
function ascribed to them) rhythms are defined (r-0
to r-9
). These are then turned into longer sequences
using Lisp's backquote and at-comma syntax for list splicing.
The terms "cantus", "altus", "tenor", and "bassus" are used to indicate the four voices, and the terms "cantizans", "altizans", "tenorizans", and "bassizans" are used to denote specific voice-leading structures.
;; time signature (ts '((4 4))) ;; basic rest rhythm (r-0 '((w))) ;; various multi-bar rest sequences (2-bars-rest-ts `((,@ts ,@r-0) ,r-0)) (5-bars-rest-ts `((,@ts ,@r-0) ,r-0 ,r-0 ,r-0 ,r-0)) ;; first three bare (functionless) rhythm sequences (r-1 '(q h q)) (r-2 '(+q q h)) (r-3 '(h h)) ;; BASIC FAUXBOURDON RHYTHM SEQ ;; assigning functions (as variable names) to bare rhythm sequences (5-6-sus-7-6 `(,@r-1)) (tie-to-cantizans `(,@r-2)) ;; creating variables for fauxbourdon voices (sus-7-6-to-half-cadence-cntzns-ts `((,@ts ,@5-6-sus-7-6) ,tie-to-cantizans)) (four-halves-to-half-cadence-altzns-tnzns-ts `((,@ts ,@r-3) ,r-3)) ;; fourth and fifth bare rhythms (r-4 '(w)) (r-5 '(- e e - q h)) ;; BASIC 2-BAR RUGGIERO RHYTHM SEQ ;; assigning functions (as variable names) to bare rhythm sequences (cantizans-interruptus `(,@r-1)) (whole `(,@r-4)) ;; creating variables for 2-bar ruggiero voices (2-bar-cadence-cantizans-ts `((,@ts ,@cantizans-interruptus) ,whole)) (2-bar-cadence-tenorizans-ts `((,@ts ,@r-5) ,whole)) (2-bar-cadence-bassizans-ts `((,@ts ,@whole) ,whole)) ;; sixth bare rhythm (r-6 '(q q h)) ;; assigning functions (as variable names) to bare rhythm sequences (tenorizans-interruptus-1 `(,@r-2)) (tenorizans-interruptus-2 `(,@r-6)) ;; BASIC 5-BAR FALLING-5THS RUGGIERO SEQUENCE ;; creating variables for 5-bar falling-5ths ruggiero voices (5-bars-falling-fifths-cntzns-tnzs-ts `((,@ts ,@cantizans-interruptus) ,tenorizans-interruptus-1 ,cantizans-interruptus ,tenorizans-interruptus-1 ,whole)) (5-bars-falling-fifths-tnzs-cntzns-ts `((,@ts ,@tenorizans-interruptus-2) ,cantizans-interruptus ,tenorizans-interruptus-1 ,cantizans-interruptus ,whole)) (5-bars-falling-fifths-bsszns-ts `((,@ts ,@whole) ,whole ,whole ,whole ,whole)) ;; seventh bare rhythm (r-7 '(h. q)) ;; 3-BAR RUGGIERO IV-I-V-I MODULATION SEQUENCE ;; creating variables for 3-bar ruggiero IV-I-V-I modulation voices (two-halves `(,@r-3)) ;; eighth and ninth bare rhythms (r-8 '(q q q q)) (r-9 '(q q q. e)) ;; NEIGHBOR ELABORATIONS FOR RUGGIERO VOICES ;; creating elaborated upper-/lower-neighbor 2-bar bassizans (2-bar-upper-lower-neighbor-octave-bassizans-ts `((,@ts ,@r-8) ,whole)) ;; creating elaborated lower-neighbor 2-bar altizans and tenorizans (2-bar-lower-neighbor-altizans-ts `((,@ts ,@r-9) ,whole)) (2-bar-lower-neighbor-tenorizans-ts `((,@ts ,@r-6) ,whole)) ;; BASIC 2-BAR SEKUND-AKKORD SEQUENCE ;; creating variable for basic sekund-akkord sequence (sekund-core `((,@ts ,@r-3) ,whole)) ;; BASIC 2-BAR PEDAL-POINT SEQUENCE ;; creating variables for the inner and outer voices of the basic 2-bar ;; pedal-point sequence (2-bar-pedal-sustained-outer-voices-ts `((,@ts ,@whole) ,whole)) (2-bar-pedal-moving-inner-halves-ts `((,@ts ,@r-3) ,whole))
+ The call to make-slippery-chicken
The call to make-slippery-chicken
is then also made
within the let*
enclosure, and its resulting object is
stored in the variable tonal-example
.
The majority of the keyword arguments within the function are set by
using the variables defined above. Only
the rthm-seq-palette
is defined within the function
itself.
(tonal-example (make-slippery-chicken '+tonal-example+ :title "tonal example" :tempo-map '((1 (q 112))) :ensemble '(((vo (violin :midi-channel 1)) (vt (violin :midi-channel 2)) (va (viola :midi-channel 3)) (vc (cello :midi-channel 4)))) :set-palette sp :set-map `((1 ,sm)) :set-limits-low (first transposed-lims-lo-hi) :set-limits-high (second transposed-lims-lo-hi) :avoid-melodic-octaves nil :avoid-used-notes nil [...] :rthm-seq-map `((1 ,rsm))))
+ The individual voices in the rthm-seq-palette
Each rthm-seq
in the rthm-seq-palette
then consists of one voice of one of the tonal 3- and 4-voiced
modules. They are grouped together according to similar types
(fauxbourdon, ruggiero etc.)
How the IDs are created
The IDs of these rthm-seqs
are selected based on the
module type, the variation number, the mode, and the voice. In this
case, as above, the top voice is abbreviated "c" for cantus;
and the remaining voices are abbreviated "a", "t", and "b" for
"altus", "tenor", and "bassus". These IDs are used in
the combos-al
variable defined above when automatically
generating the rthm-seq-map
Splicing in the variables for longer rhythm patterns
The majority of the rthm-seq
objects defined here make
use of the variables declared above for the multi-bar combinations
of repeating rhythm patterns. They are spliced into the definition
of the rthm-seq
objects using Lisp's backquote and
comma syntax.
(fx-1-maj-c (,sus-7-6-to-half-cadence-cntzns-ts
Implementing the pitches-to-pseq function
The rthm-seq
objects defined in this piece each have
only one pitch-seq
in
their pitch-seq-palette
. This one pitch-seq
always applies to one of the two core-set-
variables,
which are both the C/0 transpositions of the given sets. By
populating the set-palette
with exact copies of the
original two sets, which are merely transposed, the interval
structure of each set will always remain the same, and
the pitch-seq
numbers will always return the pitch on
the same scale degree in the transposed set.
The pitch-seq
numbers are generated automatically
using the pitches-to-pseq
function defined
above. Each :pitch-seq-palette
is created by splicing
the results of calling that function with specified note-name
symbols (as they would apply to the C/0 transposition of the module)
and the core-set variable to which they apply into
the pitch-seq-palette
, again using Lisp's backquote and
comma syntax.
:pitch-seq-palette (,(pitches-to-pseq '(g4 a4 g4 fs4 g4) core-set-maj))))
The rthm-seq-palette
in full:
:rthm-seq-palette ;; The rthm-seqs: "-c" means cantus, "-a" means alto, "-t" means ;; tenor, "-b" means bass. `((2-rest (,2-bars-rest-ts)) (5-rest (,5-bars-rest-ts)) ;; FAUXBOURDON SEQUENCES ;; fauxbourdon 1 major (fx-1-maj-c (,sus-7-6-to-half-cadence-cntzns-ts :pitch-seq-palette (,(pitches-to-pseq '(g4 a4 g4 fs4 g4) core-set-maj)))) (fx-1-maj-a (,four-halves-to-half-cadence-altzns-tnzns-ts :pitch-seq-palette (,(pitches-to-pseq '(e4 d4 c4 b3) core-set-maj)))) (fx-1-maj-t (,four-halves-to-half-cadence-altzns-tnzns-ts :pitch-seq-palette (,(pitches-to-pseq '(c4 b3 a3 g3) core-set-maj)))) ;; fauxbourdon 1 minor (fx-1-min-c (,sus-7-6-to-half-cadence-cntzns-ts :pitch-seq-palette (,(pitches-to-pseq '(g4 af4 g4 fs4 g4) core-set-min)))) (fx-1-min-a (,four-halves-to-half-cadence-altzns-tnzns-ts :pitch-seq-palette (,(pitches-to-pseq '(ef4 d4 c4 b3) core-set-min)))) (fx-1-min-t (,four-halves-to-half-cadence-altzns-tnzns-ts :pitch-seq-palette (,(pitches-to-pseq '(c4 bf3 af3 g3) core-set-min)))) ;; fauxbourdon 2 major (fx-2-maj-t (,four-halves-to-half-cadence-altzns-tnzns-ts :pitch-seq-palette (,(pitches-to-pseq '(c3 b2 a2 g2) core-set-maj)))) ;; fauxbourdon 2 minor (fx-2-min-t (,four-halves-to-half-cadence-altzns-tnzns-ts :pitch-seq-palette (,(pitches-to-pseq '(c3 bf2 af2 g2) core-set-min)))) ;; fauxbourdon 3 major (fx-3-maj-c (,four-halves-to-half-cadence-altzns-tnzns-ts :pitch-seq-palette (,(pitches-to-pseq '(e5 d5 c5 b4) core-set-maj)))) ;; RUGGIERO SEQUENCES ;; ruggiero 1 major (rg-1-maj-c (,2-bar-cadence-cantizans-ts :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 b4 c5) core-set-maj)))) (rg-1-maj-a (,2-bar-cadence-tenorizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g4 f4 e4 d4 c4) core-set-maj)))) (rg-1-maj-t (,2-bar-cadence-bassizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g3 c3) core-set-maj)))) ;; ruggiero 1 minor (rg-1-min-c (,2-bar-cadence-cantizans-ts :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 b4 c5) core-set-min)))) (rg-1-min-a (,2-bar-cadence-tenorizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g4 f4 ef4 d4 c4) core-set-min)))) (rg-1-min-t (,2-bar-cadence-bassizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g3 c3) core-set-min)))) ;; ruggiero 2 major (rg-2-maj-c (,5-bars-falling-fifths-cntzns-tnzs-ts :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 bf4 a4 g4 a4 bf4 af4 g4 f4 ef4) core-set-maj)))) (rg-2-maj-a (,5-bars-falling-fifths-tnzs-cntzns-ts :pitch-seq-palette (,(pitches-to-pseq '(f4 e4 d4 e4 f4 ef4 d4 c4 d4 ef4 d4 ef4) core-set-maj)))) (rg-2-maj-t (,5-bars-falling-fifths-bsszns-ts :pitch-seq-palette (,(pitches-to-pseq '(g3 c3 f3 bf2 ef3) core-set-maj)))) ;; ruggiero 2 minor (rg-2-min-c (,5-bars-falling-fifths-cntzns-tnzs-ts :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 bf4 a4 g4 a4 bf4 af4 gf4 f4 ef4) core-set-maj)))) (rg-2-min-a (,5-bars-falling-fifths-tnzs-cntzns-ts :pitch-seq-palette (,(pitches-to-pseq '(f4 e4 d4 e4 f4 ef4 d4 c4 d4 ef4 d4 ef4) core-set-maj)))) (rg-2-min-t (,5-bars-falling-fifths-bsszns-ts :pitch-seq-palette (,(pitches-to-pseq '(g3 c3 f3 bf2 ef3) core-set-maj)))) ;; ruggiero 3 major (rg-3-maj-c (((,@ts ,@cantizans-interruptus) ,whole ,tie-to-cantizans) :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 bf4 a4 gs4 a4) core-set-maj)))) (rg-3-maj-a (((,@ts ,@r-6) ,tenorizans-interruptus-1 ,two-halves) :pitch-seq-palette (,(pitches-to-pseq '(g4 g4 g4 f4 e4 e4 e4) core-set-maj)))) (rg-3-maj-t (((,@ts ,@r-6) ,r-7 ,two-halves) :pitch-seq-palette (,(pitches-to-pseq '(d4 e4 d4 d4 c4 b3 cs4) core-set-maj)))) (rg-3-maj-b (((,@ts ,@whole) ,two-halves ,two-halves) :pitch-seq-palette (,(pitches-to-pseq '(g3 d3 a3 e3 a2) core-set-maj)))) ;; ruggiero 4 minor (rg-4-min-c (,2-bar-cadence-cantizans-ts :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 b4 c5) core-set-min)))) (rg-4-min-a (,2-bar-cadence-tenorizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g4 f4 ef4 d4 c4) core-set-min)))) (rg-4-min-b (,2-bar-upper-lower-neighbor-octave-bassizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g3 fs3 g3 g2 c3) core-set-min)))) ;; ruggiero 5 minor (rg-5-min-c (,2-bar-cadence-cantizans-ts :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 b4 c5) core-set-min)))) (rg-5-min-a (,2-bar-lower-neighbor-altizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g4 fs4 g4 f4 e4) core-set-min)))) (rg-5-min-t (,2-bar-lower-neighbor-tenorizans-ts :pitch-seq-palette (,(pitches-to-pseq '(d4 c4 d4 c4) core-set-min)))) (rg-5-min-b (,2-bar-upper-lower-neighbor-octave-bassizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g3 af3 g3 g2 c3) core-set-min)))) ;; ruggiero 6 major (rg-6-maj-c (,2-bar-cadence-tenorizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g5 f5 e5 d5 c5) core-set-maj)))) (rg-6-maj-a (,2-bar-cadence-cantizans-ts :pitch-seq-palette (,(pitches-to-pseq '(b4 c5 b4 c5) core-set-maj)))) (rg-6-maj-b (,2-bar-cadence-bassizans-ts :pitch-seq-palette (,(pitches-to-pseq '(g3 c3) core-set-maj)))) ;; SEKUND SEQUENCES ;; sekund 1 major (sek-1-maj-c (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(e5 e5 f5) core-set-maj)))) (sek-1-maj-a (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(c5 c5 c5) core-set-maj)))) (sek-1-maj-t (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(g4 g4 f4) core-set-maj)))) (sek-1-maj-b (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(c3 bf2 a2) core-set-maj)))) ;; sekund 1 major (sek-2-maj-c (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(e5 fs5 g5) core-set-maj)))) (sek-2-maj-a (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(c4 d5 d5) core-set-maj)))) (sek-2-maj-t (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(g4 a4 g4) core-set-maj)))) (sek-2-maj-b (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(c3 c3 b2) core-set-maj)))) ;; sekund 3 minor (sek-3-min-c (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(ef5 e5 f5) core-set-min)))) (sek-3-min-a (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(c5 c5 c5) core-set-min)))) (sek-3-min-t (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(g4 g4 f4) core-set-min)))) (sek-3-min-b (,sekund-core :pitch-seq-palette (,(pitches-to-pseq '(c3 bf2 af2) core-set-min)))) ;; PEDAL SEQUENCES ;; pedal 1 major (ped-1-maj-c (,2-bar-pedal-sustained-outer-voices-ts :pitch-seq-palette (,(pitches-to-pseq '(c5 c5) core-set-maj)))) (ped-1-maj-a (,2-bar-pedal-moving-inner-halves-ts :pitch-seq-palette (,(pitches-to-pseq '(g4 a4 g4) core-set-maj)))) (ped-1-maj-t (,2-bar-pedal-moving-inner-halves-ts :pitch-seq-palette (,(pitches-to-pseq '(e4 f4 e4) core-set-maj)))) (ped-1-maj-b (,2-bar-pedal-sustained-outer-voices-ts :pitch-seq-palette (,(pitches-to-pseq '(c3 c3) core-set-maj)))))
+ Enharmonic spellings that reflect the tonality
By default, slippery chicken will automatically attempt to respell all of the pitches in the resulting score in a manner that makes sense in an atonal setting. The result of that automatic procedure in this example results in a number of pitches needing to be enharmonically flipped, in order to accurately reflect their function within a tonal setting.
There are three options for changing the enharmonic pitches of one
or more notes in a slippery-chicken
object:
the respell-notes
,
enharmonic
,
and
enharmonics
methods. This example uses the latter two methods.
In order for these three methods to be effective, they must be
called as post-generation editing methods prior to the calls to any
of the printable output methods, and therefore require that
the respell-notes
option of cmn-display
and write-lp-data-for-all
be set
to NIL
.
(cmn-display tonal-example :file "/tmp/tonal.eps" :auto-bar-nums 5 :respell-notes nil :auto-clefs nil) (write-lp-data-for-all tonal-example :respell-notes nil :auto-clefs nil))
enharmonics
The enharmonics
method allows the user to specify
a range of measures in which pitches are to be enharmonically
flipped, as well as a list of note-name symbols that are to be
affected. The code for this piece therefore uses a loop in which the
starting and ending bars, the player, and the list of pitches, are
passed as a for
value to the macro, and
a do
statement that applies
the enharmonics
method with these different values at
each iteration. This method does not allow for the enharmonic
spelling of "white-key" notes (e.g. C as B-sharp etc.)
(loop for d in '((14 16 vo (cs5)) (15 16 vt (fs4)) (19 27 vt (af4)) (25 25 va (af4)) (27 28 vo (cs5)) (27 30 vt (fs4 cs5)) (27 30 va (fs4)) (42 45 vo (cs5)) (43 44 vt (fs4)) (47 59 vo (bf5 af5)) (47 59 vt (ef5 af5)) (47 59 va (bf4 ef5)) (61 65 vo (ef5)) (61 66 vt (bf4 af4)) (70 70 vo (af5))) do (enharmonics tonal-example (first d) (second d) (third d) :pitches (fourth d)))
enharmonic
The enharmonic
method always applies to only one
sounding event object at a time. The
optional :force-naturals
argument of this method allows
the method to be used to respell "white-key" pitches as enharmonic
equivalents. The method is used here as well within a loop
whose for
statement specifies a list of sublists
consisting of the measure number, event number and player ID of the
notes to be altered.
(loop for en in '((17 1 vt) (17 3 vo) (17 3 vt) (20 2 vo) (20 2 vt) (21 1 vo) (21 3 vo) (21 2 vc) (24 2 vo) (24 2 vt) (25 1 vo) (25 3 vo) (25 2 vt) (26 1 vt) (27 2 vt) (28 2 va) (29 2 vo) (29 3 vo) (29 1 vc) (30 2 vt) (45 2 vo) (45 3 vo) (45 1 vt) (45 3 vt) (47 2 vo) (48 1 vc) (49 1 vt) (49 3 vt) (52 1 vc) (53 1 vt) (53 3 vt) (57 2 vo) (58 1 vc) (59 1 vt) (59 3 vt) (62 2 vo) (63 1 vo) (62 2 vt) (61 2 va) (62 1 va)) do (enharmonic (get-event tonal-example (first en) (second en) (third en)) :force-naturals t))
+ Inserting key changes and double-bars
Another small loop is used to insert key changes and double-bar lines into the printable output. It is important to point out here that CMN always prints the accidentals into the score, even if a key signature has been added; whereas, LilyPond will hide accidentals already present in a key signature and automatically add cautionary accidentals where suited.
This little loop takes care of both the key signature and the double-bar placement at the same time.
(loop for b-k in '((12 (ef major)) (19 (fs minor)) (27 (gf major)) (32 (ef major)) (47 (fs major)) (68 (a minor))) do (loop for p in '(vo vt va vc) do (change-bar-line-type tonal-example (1- (first b-k)) 1) (add-mark-before (get-event tonal-example (first b-k) 1 p) `(key ,@(second b-k)))))
+ Changing staff names for same instruments
The final step undertaken by the code for this piece is to change
the instrument staff names of the two violin parts for the printable
output. By default these will both be identified in the score
as violin. The following little loop uses setf
to change the value of the staff-name
and staff-short-name
slots of the corresponding
instrument objects stored in this slippery-chicken
object's ensemble
slot to violin 1
and violin 2.
(loop for n in '((vo "violin 1" "vln 1") (vt "violin 2" "vln 2")) do (setf (staff-name (get-data-data (first n) (ensemble tonal-example))) (second n)) (setf (staff-short-name (get-data-data (first n) (ensemble tonal-example))) (third n)))
+ Output
The output for this piece is generated in the same way already
covered in other tutorials. Of note here is the use of
the auto-bar-nums
argument to
the cmn-display
method, which causes bar numbers to be
placed into the score at intervals of 5 measures, and the setting of
both the respell-notes
and auto-clefs
options to NIL
in both the cmn-display
and write-lp-data-for-all
methods. The former is
necessary in order for the enharmonic changes described above to be
effective. The latter is merely a matter of preference.