Lists: construction and deconstruction.

• So far we've only used lists to represent function calls. We can use lists to represent collections of data. For example we can use a list to represent a list of notes or rhythms:
'(d4 c4) '(q. q.)

• we can use lists of lists to represent measures in a composition:
(defvar A-notes '((d4 c4)(b3 c4 b3 c4 a3)(b3 a3 b3 c4 b3)(c4)
(e4 d4 c4 b3 a3)(g3 r a3 b3)(c4 g3 r a3)(f3 r a3 b3)(c4 a3 r c4)
(b3 r d4 e4)(f4 e4 d4 c4 b3)(d4)))
(defvar A-rhythm '((q. q.)(e e e e q)(e e e e q)(h.)
(e e e q e)(q. e e e)(e q e q)(q. e e e)(e q e q)
(q. e e e)(e e e q e)(h.)))

(defvar B-notes '((e4 d4)(c4 b3)
(c4 b3 c4 a3 b3)(c4 b3 c4 d4)(e4 d4 c4 b3 a3)(g3 r a3 b3)(c4 b3 r e4)
(d4 r e4 fs4)(g4 c4 r d4)(e4 r g4 e4)(f4 e4 d4 c4 b3)(c4)))
(defvar B-rhythm '((q. q.)(q. q.)
(e e e e q)(q e q e)(e e e q e)(q. e e e)(e q e q)
(q. e e e)(e q e q)(q. e e e)(e e e q e)(h.)))

(setf q 0.5
q. (* q 1.5)
e (/ q 2.0)
h. (* q. 2))

• and, furthermore, we can use lists to represent the form of a piece:
(defvar rondeau '(A B pa1 A pa2 pb A B))

• The function list can be used to create lists of elements:
(defvar A (list A-notes A-rhythm "Quant ma dame les maus d'amer m'aprent"))
(defvar B (list B-notes B-rhythm "Elle me puet aussi le biens aprendre"))
(defvar pa1 (list A-notes A-rhythm "Qu'en grant douceur mon cuer tient et esprent"))
(defvar pa2 (list A-notes A-rhythm "Dont qui les biens a droit saveure"))
(defvar pb (list B-notes B-rhythm "Rien n'est plus dous; c'est legier a comprendre"))

• The previous Lisp expressions represent the music of a Rondeau, a "fixed form" song of the 14th century. The composer of the piece is Guillaume de Machaut, and the piece tile is Quant ma Dame. Here is the score of the piece:     • As our rondeau is a compound data structure (a list of lists), we need to be able to access the individual elements and sub-lists of a list. There are many built-in Lisp functions for this purpose, and they can be used in conjunction with one another in a great many ways:
• car takes a single list argument and returns its first element:
(car rondeau)

• first is identical to car:
(first rondeau)

• cdr takes a single list argument and returns the same list minus its first element:
(cdr rondeau)

• rest is identical to cdr:
(rest rondeau)

• second takes a single list argument and returns the second element of it:
(second rondeau)

Note that this is identical to do:

(car (cdr rondeau))

or:

• third takes a single list argument and returns the third element of it:
(third rondeau)

Note that this is identical to do:

(car (cdr (cdr rondeau)))

or:

• Observe that the element(s) to be returned by each of the element-accessing functions we have described is specified implicitly in their names. Often it is convenient to be able to specify the desired list element(s) explicitly in terms of the position in the list.

• The function nth takes an index and a list and returns the element of the list at that index:
(nth 4 rondeau)

• The function nthcdr takes an index and a list and returns the portion of the list that starts at that index:
(nthcdr 5 rondeau)

• To know the length of a list we can use the Lisp function list-length
(list-length rondeau)

• lets say we want to print sequentially the elements of rondeau:
(loop for i from 0 below (list-length rondeau) do (print (nth i rondeau)))

• If we want to have the poetry of the piece we need the third element of the lists that represent the parts of the form:
(loop for i from 0 below (list-length rondeau) do
(print (third (eval (nth i rondeau)))))

Note that we need to use eval to get the content of the symbols in rondeau.

• To print a list of notes and rhythms we can do:
(loop for i from 0 below (list-length rondeau) do
(let ((note-list (first (eval (nth i rondeau))))
(rhythm-list (second (eval (nth i rondeau)))))
(loop for measure from 0 below (list-length note-list) do
(format t "~S ~S~%" (nth measure note-list)(nth measure rhythm-list)))))

• We can use the function dolist to print the content of all the elements of the form:
(loop for i from 0 below (list-length rondeau) do
(dolist (element (eval (nth i rondeau)))
(print element)))

Save, compile and load this file to get the CLM instrument to play the piece.
• And finally we can play our piece using the simp-clm-env instrument:
(with-sound (:output "/zap/rondeau.snd" :play nil)
(let ((time 0.0))
(loop for i from 0 below (list-length rondeau) do
(let ((note-list (first (eval (nth i rondeau))))
(rhythm-list (second (eval (nth i rondeau)))))
(loop for measure from 0 below (list-length note-list) do
(let ((note-measure (nth measure note-list))
(rhythm-measure (nth measure rhythm-list)))
(loop for note from 0 below (list-length note-measure) do
(let ((note-to-play (nth note note-measure))
(rhythm-to-play (nth note rhythm-measure)))
(if (equal 'r note-to-play)
()
(simp-clm-env time (eval rhythm-to-play) (pitch note-to-play) 0.3))
(incf time (eval rhythm-to-play))))))))))

Arrays: a short introduction.

• Arrays can have one or more dimensions in Lisp. Arrays of one dimension ar also call vectors. The make-array macro is used to create arrays:
(defvar c-major-scale (make-array 7))

C-MAJOR-SCALE

• The make-array macro take at least one argument: the array size. The array c-major-scale is empty, i.e. its seven positions are available to store data.

• To access to one position of an array we use the function aref:
(aref c-major-scale 2)

• Lisp returns NIL because our array is empty (NIL = false o nothing). Note that the aref function acts as a pointer to a position of the array, it can be used both to access data from that position or to set data to that position combined with setf:
(setf (aref c-major-scale 0) 'c4)

Note that array positions starts form zero (as list positions).

(aref c-major-scale 0)

• We can transfer data stored in a list to an array:
(defvar c-major-notes '(c4 d4 e4 f4 g4 a4 b4))

(loop for n from 0 below (list-length c-major-notes) do
(setf (aref c-major-scale n) (nth n c-major-notes)))

• Now we can access our scale by degrees:
(loop for degree from 0 below (length c-major-scale) do
(print (aref c-major-scale degree)))

or:

(with-sound ()
(loop for degree from 0 below (length c-major-scale) and start from 0 by 0.5 do
(simp-clm-env start 0.5 (pitch (aref c-major-scale degree)) 0.1)))

• The function length gives us the size of an array (as list-length gives us the size of a list).

• Arrays (as lists) can contain any type of elements:
(defvar quant-ma-dame (make-array 3))
(setf (aref quant-ma-dame 0) "Machaut")
(setf (aref quant-ma-dame 1) 14)
(setf (aref quant-ma-dame 2) 'rondeau)

quant-ma-dame

• Using make-array we can specify an element-type for the elements of an array, and an initial-element:
(defvar c-major-frequency (make-array 7 :element-type 'single-float :initial-element 0.0))

c-major-frequency

Note that the new array is full of O.0 instead of NIL.

(loop for n from 0 below (list-length c-major-notes) do
(setf (aref c-major-frequency n) (pitch (nth n c-major-notes))))

c-major-frequency

• In CLM we can use arrays to store any kind of objects (envelopes, oscillators, filters, etc). In the following example we create an array of 7 oscillators with the frequencies stored in our c-major-frequency array.
(let ((array-size (length c-major-frequency))
(osc-array (make-array (length c-major-frequency) :element-type 'osc)))
;;; fill 'er up...
(loop for j from 0 below array-size do
(setf (aref osc-array j) (make-oscil :frequency (aref c-major-frequency j))))
;;; and show the array
osc-array)

• Expressions similar to the previous one are frequently found in CLM's additive synthesis instruments.
Back to LispWorkshop main page.