Temporal recursion

Slub have a number of important livecoding transmissions coming up (including a performance at the Mozilla Festival!) so it’s time to work on fluxus/fluxa/scheme bricks. Here are some recording tests of a feature I’ve been wanting to use for a long time – temporal recursion.

These recordings were not changed by hand as they played, but started and left running in ‘generative audio’ mode in order to try and understand the technique. This method of sequencing is inspired by Impromptu which uses a similar idea. In fluxa it’s all based around a single new function: “in” which schedules a call to a function – which can be the current function (this is different to the existing ‘timed tasks’ in fluxus which are less precise for this kind of sequencing).

(define (tick time a)
    (play (+ time 3) (sample "ga.wav" (note (pick '(40 42 45) a))))
    (in time 0.22 tick (+ a 1)))

(in (time-now) 1 tick 0)

The “in” function takes the current time, the time to wait before the call, the function to call and it’s parameters. In the example above the argument “a” gets incremented each time, resulting in a sequence of notes being played. Recursion generally brings up thoughts of self similarity and fractal patterns – as in the graphical use of recursion in fluxus, but here it’s better to imagine a graph of function calls. Each function can branch to a arbitrary number of others, so limitations have to be put in place to stop the thing exploding with too many concurrent calls. What seems to happen (even with small function call graphs) is the appearance of high level time structures – state changes and shifts into different modes where different patterns of calls lock into sequence. You can hear this clearly in the second recording above which alters itself half way through.

I’ve also experimented with visualising the call graph, with limited success with this more complex example – the round nodes are functions, the boxes parameter changes and labels on the connections are the branch conditions:

(require fluxus-018/fluxa)

(searchpath "/home/dave/noiz/nm/")
(define n '(23 30))
(set-scale '(1 1 2 1))

(define (za time a b)
    (play (+ time 3) (mul (mul (adsr 0 0.1 1 1) (pick '(0.4 0.1 1 0.4 1) a)) 
        (sample "ga.wav" (note (- (modulo (- b a) 17) (* (pick n a) 2))))) -0.5)
    (when (zero? (modulo a 16)) (in time 0.3 zm a b))
    (if (eq? (modulo a 14) 12)
        (in time 1.0 zoom a (- b 1))
        (in time (- 0.5 (* (modulo a 13) 0.03)) za (+ a 1) (+ b 2))))

(define (zm time a b)
    (play (+ time 3) (mul (mul (adsr 0 0.1 1 1) (pick '(0.1 0.4 1) a))
        (sample "ga.wav" (note (+ (modulo b 5) (/ (pick n a) 2))))) 0.5)
    (if (> a 12)
        (in time 1.0 za b a)
        (in time (pick '(1.3 1.5 0.5) a) zm (+ a 1) b)))

(define (zoom time a b)
    (play (+ time 3) (mul (mul (adsr 0 0.1 1 1) (pick '(0.1 0.2 0.3) a)) 
        (sample "ga.wav" (note (+ (pick n a) (* 3 (modulo a (+ 1 (modulo b 5)))))))))
    (if (> a 16) 
        (in time 0.3 zm 0 (+ b 1))
        (in time (pick '(1.3 0.12) a) zoom (+ a 1) b)))

(in (time-now) 1 zoom 0 1)

Algorithmic fungi patterns

Central to the borrowed scenery game is an ecosystem of fungi that players will grow to feed the plants found by foragers in the city of Ghent using Boskoi. These fungi will work differently from the plants in Germination X, partly in response to some interesting game testing feedback – the fungi will only grow to the next stage when activated by a player, but the positions of new fungi will be algorithmically decided.

This calls on a kind of cellular automata, which requires a bit of prototyping to get the right values to make the kind of patterns I want. I mocked up something in clojure (so I can plug it into the game server easily) using ascii art for previewing the patterns resulting, the four stages of fungi are:

. : spore
o : young
O : adult
* : fruiting

Each fungi cell only goes from adult to fruiting if conditions are right (counting number of neighbours as in the game of life), after which 5 spores are created around it. After fruiting or adult stage the fungi dies and gets removed. This is a screenshot showing some different colonies which emerge starting with just two fungi spores:

                     .Ooo*.                o..O   
                     o*o ..                 *.O   
                      ..*.                 o*oo.  
                       ...                 .*OOoo 
                                            ..Oo  
                  O o                       ..*.  
                  O                            o  
              O       o                           
                oO                                
            .O                                    
           O*                                     
           oO..                                   
         o  o *.OO                                
             ..O                O O               
              ** o                O               
             .*..o              OooO              
             .o.*.             .oOO*O O           
               . .              ..O*O O.          
                                O*. O*o*.         
                                Oo OoO. .     

And here is the code:

(def *spore-count* 5)
(def *neighbour-distance* 15)
(def *max-neighbour* 60)
(def *min-neighbour* 0)

(defn make-cell-world [cells]
  {:cells cells})

(defn make-cell [x y]
  {:x x :y y
   :state "."})

(defn cell-dist [cell-a cell-b]
  (let [v {:x (- (:x cell-a) (:x cell-b))
           :y (- (:y cell-a) (:y cell-b))}]
    (Math/sqrt (+ (* (:x v) (:x v))
                  (* (:y v) (:y v))))))

(defn cell-process [cell neighbours]
  (if (< 50 (rand-int 100))
    (cond
     (= (:state cell) ".") (merge cell {:state "o"})
     (= (:state cell) "o") (merge cell {:state "O"})
     (= (:state cell) "O") (if (and (< (count neighbours) *max-neighbour*)
                                    (> (count neighbours) *min-neighbour*))
                             (merge cell {:state "*"})
                             (merge cell {:state "X"}))
     (= (:state cell) "*") (merge cell {:state "X"})
     :else cell)
    cell))

(defn cell-get-neighbours [world cell]
  (filter
   (fn [other]
     (< (cell-dist other cell) *neighbour-distance*))
   (:cells world)))

(defn cells-find [world x y]
  (filter
   (fn [cell] (and (= (:x cell) x)
                   (= (:y cell) y)))
   (:cells world)))

(defn cells-make-rnd-pos-list [cell]
  (repeatedly
   *spore-count*
   (fn [] {:x (+ (:x cell) (- (rand-int 3) 1))
           :y (+ (:y cell) (- (rand-int 3) 1))})))

(defn cells-spore [world]
  (merge world {:cells
                (concat
                 (reduce
                  (fn [r cell]
                    (if (= (:state cell) "*")
                      (reduce
                        (fn [r pos]
                          (if (empty? (cells-find world (:x pos) (:y pos)))
                            (cons (make-cell (:x pos) (:y pos)) r)
                            r))
                        r
                        (cells-make-rnd-pos-list cell))
                      r))
                  ()
                  (:cells world))
                 (:cells world))}))

(defn cells-death [world]
  (merge world {:cells
                (filter
                 (fn [cell]
                   (not (= (:state cell) "X")))
                 (:cells world))}))

(defn cells-run [world]
  (cells-death
   (cells-spore
    (merge world {:cells
                  (map
                   (fn [cell]
                     (cell-process cell (cell-get-neighbours world cell)))
                   (:cells world))}))))

(defn cell-world-print [world w h]
  (dotimes [sy h]
    (dotimes [sx w]
      (let [f (cells-find world sx sy)]
        (if (> (count f) 0)
          (print (:state (first f)))
          (print " "))))
    (print "\n")))

(defn cell-world-loop [world n]
  (println "-------------------------------------------------")
  (cell-world-print world 50 20)
  (when (> n 0)
    (recur (cells-run world) (- n 1))))


(defn -main []
  (cell-world-loop
   (make-cell-world (list
                     (make-cell 25 10)
                     (make-cell 27 10)))
   1000))