The self-similar properties of visual patterns generated by Lindenmayer-Systems can be apprehended holistically almost instantaneously. Even simple rules can generate pleasing patterns of the sort you might see on cloth or as the Kolam figures drawn in front of southeast Indian houses. One of the problems of rendering Lindenmayer-Sequences in a musi-linear form is that a self-similar pattern unfolding in time can quickly become repetitive and therefore unengaging, even when not completely predictable. To put it a different way: the difference between the visual and sonic domain here is in the amount of time and attention necessary to experience the form as a whole—the ability to step in, out, and around the synchronic visual form is entirely different from the diachronic confines or tunnel vision of the unfolding musical line.

Nevertheless, I’ve created an algorithmic approach to rendering a bass line using the Kolam generation rules as discussed in this fascinating article. The resulting musical line is below and is followed by the slippery chicken Common Lisp code which generated it.


;;; File:             kolam.lsp
;;; Class Hierarchy:  None
;;; Version:          1.0
;;; Project:          slippery chicken (algorithmic composition)
;;; Purpose:          L-System ideas based on Indian Kolam drawing rules
;;; Author:           Michael Edwards
;;; Creation date:    28th January 2013
(in-package :sc)
(in-scale :quarter-tone)

;;; so 4s and 6s below become pitch-seqs to chosen rthm-seqs of 4 and 6
;;; notes. 4 and 6 are used as sub-groups for which there are an arbitrary
;;; number of rthm-seqs sequentially numbered. The progression through these
;;; is dictated by the ocurrence of f, or forward, i.e. we move from the first
;;; to the last over the duration of the algorithm, or we use fib-trans or
;;; procession instead.
(let* ((4s '((0 3 2 6) (0 3 1 4) (0 3 4 6))) ;; a half turn to the right
       ;; a full loop to the right
       (6s '((0 4 3 -1 -3 -1) (0 3 4 1 -3 -1) (0 -1 3 6 -3 -4)))
       (num-seqs 50)
            ((a ((((4 4) q q. e q))))
             (b ((((4 4) (q) q. e e e))))
             (c ((((4 4) q. e (q) e e))))
             (d ((((4 4) q q e e (q)))))))
            ((a ((((4 4) e e e (e) e e (e) e))))
             (b ((((4 4) e (e) e e e (e) e e))))
             (c ((((4 4) e e (e) e e e (e) e))))
             (d ((((4 4) (e) e e e (e) e e e)))))))))
         '((f (f))
           (4 (4 f 6 f 4))
           (6 (4 f 6 f 6 f 6 f 4)))))
       (4keys (get-keys (get-data-data 4 rsp) nil))
       (6keys (get-keys (get-data-data 6 rsp) nil)) 
       ;; we know that there'll be an f for every 4 or 6 so we need to get
       ;; double the number of num-seqs
       ;; f has no relevance here as we're always gradually moving forward
       ;; so we could remove it as follows 
       ;; (lseq (remove 'f (get-l-sequence kolam '4 (* 2 num-seqs))))
       ;; but I'd rather use f as a repeat
       (lseq-orig (get-l-sequence kolam 4 num-seqs))
       (lseq (loop for i in lseq-orig with last = nil collect
                (if (eq i 'f) 
                  do (setf last i)))
       (num-6s (count 6 lseq))
       (num-4s (count 4 lseq))
       (4order (fibonacci-transitions num-4s 4keys))
       (6order (fibonacci-transitions num-6s 6keys))
       (rsm (loop for num in lseq collect
                 (case num
                   (4 (list 4 (pop 4order)))
                   (6 (list 6 (pop 6order)))
                   (t (error "wrong element: ~a" num)))))
       (psp4 (make-psp '4s 4 4s))
       (psp6 (make-psp '6s 6 6s)))
  ;; trick here is that, unusually, we're going to use the same
  ;; pitch-seq-palette for each of the 4-note rthm-seqs, and sim. for the
  ;; 6-note seqs. 
  (loop for rthm-seq in (data (get-data-data 4 rsp)) do
       (setf (pitch-seq-palette rthm-seq) psp4))
  ;; with a loop counter we could also make individual psps for each rthm-seq
  ;; but sharing will guarantee circularity too
  ;;                    (make-psp '4s 4 (wrap-list 4s i))))
  (loop for rthm-seq in (data (get-data-data 6 rsp)) do
       (setf (pitch-seq-palette rthm-seq) psp6))
  ;; (setf +rsp+ rsp)
   :title "Kolam Bass" 
   :composer "Michael Edwards"
   :ensemble '(((bg (bass-guitar :midi-channel 3))))
   :tempo-map '((1 (q 240)))
   :rthm-seq-palette rsp
   :rthm-seq-map `((1 ((bg ,rsm))))
   :set-palette (make-set-palette 
                 (let* ((pitches '(c4 d4 ef4 f4 gf4 af4 bqf4 cqf5 df5))
                        (ipitches (invert-pitch-list pitches))
                        (set1 (make-stack 'set1 pitches 3))
                        (set2 (transpose (make-stack 'set2 ipitches 3) 13)))
                   (list set1 set2)))
   :set-map `((1 ,(fibonacci-transition num-seqs 'set1 'set2)))
   :set-limits-high '((bg (0 b4 100 b4)))
   :avoid-melodic-octaves t))
;;; media content generation:

;;; MIDI file
(midi-play +kolam+ :midi-file "/Users/medward2/lisp/kolam.mid")

;;; Lilypond score
;; (write-lp-data-for-all +kolam+ :base-path "/tmp/")

;;; cmn score
;;; #+ notation means only run the next Lisp form if e.g. the CMN package is
;;; available  
#+cmn (cmn-display +kolam+ :file "/Users/medward2/Dropbox/bass/kolam.eps")

;;; sound file
;; #+clm (clm-play +kolam+ 1 nil 'source-sndfile-grp-1)

;;; EOF kolam.lsp
Share Button