Monday, April 23, 2007

'(Dangling projects) - The Hamilton Cycle in the Round

Hello imaginary readers. "I am a creative person," I occasionally flatter myself by thinking. But creative people produce, and my track record for production is not so hot. So, I figured I would create a public viewing place for some of the things I am working on, both in terms of my research (theoretical neuroscience) and my personal interests (which are broad, but include a variety of less technical things which I frequently approach from a technical point of view.) Rather than make an introductory post which is all talk, I'd like to start by posting something which is, more or less, in a state of completion. Via my association with Tract a magazine whose purpose is to promote a dialog between artists and scientists which is inclusive of the general public, I came across a person (Vicki Ding - no web site but see Tract) who had used the mathematical idea of the Hamilton Cycle on a polygon to generate music. She had generated a few Hamilton Cycles and associated vertices with notes and played the resulting sequence but, the initial parts of "Godel, Escher, Bach" suggested to me an alternative way of constructing music based on a similar idea. A Hamilton Cycle is a path which visits each point on a graph (for example, a polygon) only once and also returns to the initial point after visiting all others. A cube has eight vertices, each of which can be associated with a note in a regular scale (I chose D Major, since it was the scale I first learned to play on the Cello). If we pick an arbitrary labeling of the vertices by notes in this scale, then the Hamilton Cycle defines a mapping of notes onto notes. If we apply this mapping eight times, we have returned to our original note, and visited every other note in the scale as well. Imagine now that we start with a D Major scale [d e f# g a b c# d] And we use the labeling and Hamilton Cycle shown in Figure 1 as our transformation. Then such a scale, after one application of the mapping, becomes: [e f# d d c# a g e] Following this scheme we can produce a "tune" (I use the term loosely) which is just the sequence of eight such note sequences, each of which derived from applying the Hamilton Cycle mapping on the previous sequence. This makes for some boring music, though, since all notes have the same duration. We can also label the corners of each cube with a duration, and let the Hamilton Cycle produce a sequence of durations for each of the notes. In order to maintain some sense of musical sanity, we can pick a sequence of eight durations which sum up to a whole measure's worth of time. One such sequence is [half-note quarter-note quarter-note eighth-note eighth-note eighth-note eighth-note] Now we have everything we need to make algorithmic music of a certain variety. I had worked this much out on paper but when I got to this point I wasn't sure how to proceed. I had worked to a limited extent with the synthesis environment CSOUND in college - a very irritating friend of mine (possibly a date rapist) had asked me to basically do his music theory homework assignment for him. It was based on the idea of making the music follow the behavior of relativistically interacting masses sort of shooting around in space. Busy as I was at the time I eventually passed the project onto a friend of mine (and I recently found and threw away the CD with the original data to be converted into sound), but not before learning a bit about CSOUND and making it make a few noises. Recently, though, I have been learning LiSP since so many of those who use it claim that it provides satorical insight into the workings of computer science. As far as I am concerned this remains (somewhat) to be seen, but my interest was enough to motivate me to write the program in Nyquist, a derivative of XLISP constructed especially for sound synthesis. The following program creates a "Hamilton Round" by first generating an 8 measure tune based on the Hamilton cycle and then playing it in a three part round with itself. The parts are shifted each by an octave from one another, and technically, its not really a round since we don't end up in the same octave we started in. Here is the code:

(defun mkwave ()
  (setf *table* (sim (scale 0.5 (build-harmonic 1.0 2048))
      (scale 0.25 (build-harmonic 2.0 2048))
      (scale 0.125 (build-harmonic 3.0 2048))
      (scale 0.0625 (build-harmonic 4.0 2048))))
  (setf *table* (list *table* (hz-to-step 1) T)))

(cond ((not (boundp '*mkwave*))
    (mkwave)
    (setf *mkwave* t)))

(defun note (pitch dur)
  (mult (osc pitch dur *table*) (env 0.05 0.1 0.5 1.0 0.5 0.4)))

(print "We tune because we care.")

(defun number->pitch (n)
  (cond
 ((eq 1 n) d4)
 ((eq 2 n) e4)
 ((eq 3 n) fs4)
 ((eq 4 n) g4)
 ((eq 5 n) a4)
 ((eq 6 n) b4)
 ((eq 7 n) cs4)
 ((eq 8 n) d5)))

(defun number-octave->pitch (n o)
  (+ (cond
    ((eq 1 n) d4)
    ((eq 2 n) e4)
    ((eq 3 n) fs4)
    ((eq 4 n) g4)
    ((eq 5 n) a4)
    ((eq 6 n) b4)
    ((eq 7 n) cs4)
    ((eq 8 n) d5)) (* 12 o)))

(defun number->duration (n)
  (cond
 ((eq 1 n) q)
 ((eq 2 n) q)
 ((eq 3 n) i)
 ((eq 4 n) i)
 ((eq 5 n) s)
 ((eq 6 n) s)
 ((eq 7 n) s)
 ((eq 8 n) s)))

(defun hamilton-cycle-advance (n)
  (cond
 ((eq 1 n) 4)
 ((eq 2 n) 1)
 ((eq 3 n) 6)
 ((eq 4 n) 3)
 ((eq 5 n) 8)
 ((eq 6 n) 5)
 ((eq 7 n) 2)
 ((eq 8 n) 7)))

(defun number->note (n)
  (note (number->pitch n) i))

(defun number->note-statement (n)
  `(note ,(number->pitch n) i))

(defun numbers-octave->note-statement (npitch ndur o)
  `(note ,(number-octave->pitch npitch o) ,(number->duration ndur)))

(defun rotate-list (l)
  `(,@(cdr l) ,(car l)))

(setf *pitch-seed* '(1 2 3 4 5 6 7 8))
(setf *duration-seed* '(1 2 3 4 5 6 7 8))

(let ((counter 0)
   (tones '())
   (durations '())
   (current-phrase-pitch *pitch-seed*)
   (current-phrase-duration *duration-seed*))
  (loop (cond ((<>note-statement tones durations 0)))
  (setf music1 (mapcar #'transform tones durations)))
  (setf music1 `(,@music1 ,@music1 ,@music1 ,@music1))
  (flet ((transform (tones durations)
     (numbers-octave->note-statement tones durations (/ 12 12))))
 (setf music2 (mapcar #'transform tones durations)))
  (setf music2 `(,@music2 ,@music2 ,@music2 ,@music2))
  (flet ((transform (tones durations)
     (numbers-octave->note-statement tones durations (- 0 (/ 4 4)))))
 (setf music3 (mapcar #'transform tones durations)))
  (setf music3 `(,@music3 ,@music3 ,@music3 ,@music3))
  (eval `(play (sim (seq ,@music1) (at 36 (seq ,@music2)) (at 72 (seq ,@music3))))))
You can download a copy of the source here. Because Nyquist isn't quite fast enough to generate the music in real time, I have to load the file twice to produce a nice sounding version of the output. I then used LAME to encode the wave file into an mp3. And here is that very mp3 : "music". The result is slightly reminiscent of the "Frog Round", but probably only because I have Nyquist making a pretty cheesy synth voice. The major difference seems to be that the Frog Round is very beautiful whereas this piece of music was written by a robot. Everyone knows robots don't know beauty (even if they are written in LiSP.) Comments welcome. -JVT PS - For the record, I prefer Scheme.