'******************************************************************************
'Subject: Four-piece linear cellular automata ensemble:
'         reveals what the eye can't grasp.
'         Play with 65535 generalized k = 2, r = 1.5 automata
'         (incl. the elementary and reversible CA subsets)
'          c(t+1):= f[c(t-1), c-1(t), c(t), c+1(t)]
'Author : Sjoerd.J.Schaper - vspickelen
'Date   : 04-04-2006
'Code   : FreeBasic 0.16b
'Keys   : [Esc] quit program
'         [Tab] input transition rule (Enter)
'          append "e" for an elementary CA,
'          and "r" for a reversible one
'         [Enter] random symmetric rule
'         [Ctrl]+[Enter] fully random rule
'          [i] invert rule
'          [m] mirror reflect
'         [Backspc] find cycle length
'         [Up] or [Down arrow] change window size
'         [Left] or [Right arrow] rotate grid l/r
'         [Pg Up] or [Pg Down] change voice spacing
'         [F1]...[F8] choose modal scale
'          [Space] shuffle scale notes
'         [+] or [-] change tempo
'         [s] save CA parameters [###] (Enter)
'         [l] load CA piece [###] (Enter)
'         [Insert] seed with a single alive cell
'         press any other key for a random state
'******************************************************************************
'This program is copyright (c) 2006 by the author. It is made available as is,
'and no warranty - about the program, its performance, or its conformity to any
'specification - is given or implied. It may be used, modified, and distributed
'freely, as long as the original author is credited as part of the final work.
'******************************************************************************
'
#INCLIB "winmm"
DECLARE SUB populate (t AS INTEGER)
'display the automaton at time t
DECLARE SUB evaluate ()
'apply the table of rules
DECLARE SUB inrule (n AS STRING)
'input rule number < 65536
DECLARE FUNCTION decrule () AS STRING
'convert rule to decimal
DECLARE SUB rndcell ()
'sow a single alive cell
DECLARE SUB rndstat ()
'generate sample random state
DECLARE FUNCTION converg (n AS INTEGER) AS INTEGER
'jump forward in time
DECLARE FUNCTION period (n AS INTEGER) AS INTEGER
'find cycle length < n
DECLARE SUB roll (fl AS INTEGER)
'rotate grid left/ right
DECLARE SUB scale (n AS INTEGER)
'rotate tone steps
DECLARE SUB midiout (fl AS INTEGER)
'output CA midi messages
DECLARE SUB savep (n AS INTEGER)
'save CA parameters "piece##n.par"
DECLARE FUNCTION loadp (n AS INTEGER) AS INTEGER
'load & playback CA "piece##n.par"

'winapi prototypes
DECLARE FUNCTION midClose Alias "midiOutClose" (ByVal hMidiOut As Long) As Long
DECLARE FUNCTION midiOpen Alias "midiOutOpen" (ByRef lphMidiOut As Long, _
 ByVal uDeviceID As Long, ByVal dwCallback As Long, ByVal dwInstance As Long, _
 ByVal dwFlags As Long) As Long
DECLARE FUNCTION midiMsg Alias "midiOutShortMsg" (ByVal hMidiOut As Long, _
 ByVal dwMsg As Long) As Long

CONST MODE = 12, dW = 640, dH = 480
' set screenmode, width, height

CONST MAX = 160 '                     max. grid size
DIM SHARED c(MAX, 1) AS INTEGER, ru(15) AS INTEGER, tn(23) AS INTEGER
'      cell states, transition rule, tone numbers
DIM SHARED bpm AS INTEGER, mx AS INTEGER, st AS INTEGER
'              tempo, window size, status
DIM SHARED sw AS INTEGER, vsp AS INTEGER
'       generation switch, spacer
DIM fh AS INTEGER, m AS INTEGER, n AS INTEGER, sc AS INTEGER
DIM t AS INTEGER, t0 AS SINGLE, g AS STRING, h AS STRING
RANDOMIZE TIMER
bpm = 100
mx = 19
fh = 0

t = 0
DO
   t0 = TIMER
   populate t
   midiout -1
   evaluate
   t += 1
   '
   g = INKEY$
   IF g <> "" THEN
      sc = ASC(RIGHT$(g, 1))
      IF LEN(g) > 1 THEN sc = sc + 128
      LOCATE 21, 2: PRINT SPACE$(7);
      LOCATE 22, 3: PRINT SPACE$(5);

      SELECT CASE sc
      CASE 8
         t += converg(32)
         n = period(4096) '           maximum
         IF n THEN
            LOCATE 21, 2: PRINT "period"
            LOCATE 22, 3: PRINT n; "  "
         ELSE
            LOCATE 21, 2: PRINT "fail"
         END IF
      CASE 9
         LOCATE 4, 3: INPUT " ", h
         IF h <> "" THEN
            t = 0: rndcell
            inrule (h) '              read rule
         END IF
      CASE 10, 13
         DO
            rndstat
            h = STR$(RND * 65536) '   random rule
            inrule (h)
            IF sc = 13 THEN
               ru(0) = 0 '            reflection symmetry
               ru(1) = ru(4): ru(9) = ru(12)
               ru(3) = ru(6): ru(11) = ru(14)
            END IF
            t = converg(8)
            IF period(4) = 0 THEN EXIT DO
         LOOP
      CASE 27
         EXIT DO '                    quit
      CASE 32
         FOR n = 0 TO 19 '            shuffle scale
            m = INT(RND * 20)
            SWAP tn(n), tn(m)
         NEXT n
         st XOR= 1
      CASE 43, 45
         bpm += 44 - sc '             tempo
         IF bpm < 1 THEN bpm = 1
      CASE 105
         FOR n = 0 TO mx - 1
            c(n, 0) XOR= 1: c(n, 1) XOR= 1
         NEXT n
         FOR n = 0 TO 15 '            invert
            ru(n) XOR= 1
         NEXT n
         FOR n = 0 TO 7
            SWAP ru(n), ru(15 - n)
         NEXT n
         st XOR= 1
      CASE 108
         LOCATE 21, 2: PRINT "load file#"
         LOCATE 22, 3: INPUT " ", n
         IF n = 0 THEN n = fh
         IF loadp(n) THEN t = 0
      CASE 109
         FOR n = 4 TO 6 STEP 2 '      reflect
            m = n - 3: SWAP ru(n), ru(m)
            SWAP ru(n + 8), ru(m + 8)
         NEXT n
         st XOR= 1
      CASE 115
         LOCATE 21, 2: PRINT "save file#"
         LOCATE 22, 3: INPUT " ", n
         IF n = 0 THEN n = fh + 1
         fh = n: savep n
      CASE 187 TO 196
         scale sc - 187 '             mode
      CASE 200
         mx += 1 '                    window size
         IF mx > MAX THEN mx = MAX
      CASE 201
         vsp = (vsp + 1) MOD mx '     voice spacing
         st XOR= 1
      CASE 203, 205
         roll 204 - sc '              rotate grid
      CASE 208
         mx -= 1
         IF mx < 3 THEN mx = 3
      CASE 209
         vsp = (vsp + mx - 1) MOD mx
         st XOR= 1
      CASE 210
         t = 0: rndcell '             sow
      CASE ELSE
         t = 0: rndstat '             scatter
      END SELECT

      LOCATE 4, 4: PRINT decrule$; "  ";
      LOCATE 10, 3: PRINT vsp; " ";
      LOCATE 16, 3: PRINT bpm; " ";
      IF sc = 108 OR sc = 119 THEN
         LOCATE 21, 2: PRINT SPACE$(10)
         LOCATE 22, 3: PRINT SPACE$(5)
      END IF
      WHILE INKEY$ <> "": WEND
   END IF

   t0 -= TIMER - 14.9 / bpm
   m = CINT(t0 * 1000)
   IF m > 1 THEN SLEEP m '            delay
LOOP
st XOR= 1
midiout 0
SYSTEM

'8-note Euler scale
DATA 2,2,1,1,1,2,2,1
'instrument offsets 'F F f f
DATA 29, 41, 53, 53
'bass, cello & two skinny geese
DATA &H0020C0,&H002AC1,&H0045C2,&H0038C3
'channel pan, volume & vibrato
DATA &H370AB0,&H520AB1,&H2C0AB2,&H600AB3
DATA &H5F07B0,&H5507B1,&H4A07B2,&H5007B3
DATA &H0001B0,&H0001B1,&H0B01B2,&H0001B3

FUNCTION converg (n AS INTEGER) AS INTEGER
DIM m AS INTEGER, t AS INTEGER
   m = mx * n
   FOR t = 0 TO m '                   kill transients
      evaluate
   NEXT t
converg = m
END FUNCTION

FUNCTION decrule AS STRING
DIM r AS INTEGER, s AS INTEGER, t AS INTEGER
DIM v AS INTEGER, g AS STRING
   v = ru(15)
   FOR t = 14 TO 0 STEP -1 '          convert to decimal
      v = v SHL 1 OR ru(t)
   NEXT t

   r = v AND 255: s = v SHR 8
   IF (r XOR s) = 0 THEN
      g = STR$(r) + "e"
   ELSE
      s = (NOT s) AND 255
      IF (r XOR s) = 0 THEN
         g = STR$(r) + "r"
      ELSE
         g = STR$(v)
      END IF
   END IF

decrule = g
END FUNCTION

SUB evaluate
DIM k AS INTEGER, u AS INTEGER
DIM ul AS INTEGER, ur AS INTEGER
   ul = c(mx - 1, sw)
   c(mx, sw) = c(0, sw) '             wrap edges

   FOR k = 0 TO mx - 1
      ul OR= c(k, 1 - sw) SHL 1 '     previous(k)
      u = c(k, sw)
      ur = c(k + 1, sw)
      ur OR= (ul SHL 1 OR u) SHL 1 '  compute context(k)
      ul = u: c(k, 1 - sw) = ru(ur) ' update CA
   NEXT k

   sw XOR= 1 '                        switch generations
END SUB

SUB inrule (h AS STRING)
DIM s AS INTEGER, t AS INTEGER
   s = VALINT(h) AND 65535
   IF s < 256 THEN
      IF INSTR(h, "e") THEN
         s OR= s SHL 8 '              elementary k = 2, r = 1
      ELSEIF INSTR(h, "r") THEN
         t = (NOT s) AND 255
         s OR= t SHL 8 '              second-order rule
      END IF
   END IF

   FOR t = 0 TO 15
      ru(t) = s AND 1 '               convert to binary
      s SHR= 1
   NEXT t
END SUB

FUNCTION loadp (n AS INTEGER) AS INTEGER
DIM g AS STRING, h AS STRING, r AS INTEGER, t AS INTEGER
DIM tx AS INTEGER, u AS INTEGER, v AS INTEGER
IF n < 0 OR n > 999 THEN n = 0
g = ".\piece000.par": h = STR$(n)
MID$(g, 11 - LEN(h)) = h

IF OPEN(g FOR INPUT AS #1) = 0 THEN
   sw = 0: DO
      IF EOF(1) THEN EXIT DO
      LINE INPUT #1, g: g = TRIM$(g)
      SELECT CASE LEFT$(g, 2)
      CASE "#R"
         inrule MID$(g, 3)
      CASE "#W"
         mx = VALINT(MID$(g, 3))
         IF mx < 3 OR mx > MAX THEN mx = MAX
      CASE "#S"
         g = LTRIM$(MID$(g, 3))
         tx = LEN(g) \ 2 - 1
         FOR t = 0 TO tx
            r = t * 8
            v = VALINT("&H" + MID$(g, 2 * t + 1, 2))
            FOR u = 0 TO 7
               c(r + u, sw) = v AND 1
               v SHR= 1
            NEXT u
         NEXT t: sw = 1 '             state
      CASE "#V"
         vsp = VALINT(MID$(g, 3))
         IF vsp < 0 THEN vsp = 0
      CASE "#N"
         g = LTRIM$(MID$(g, 3)) '     notes
         FOR t = 0 TO 3
            r = t * 6
            v = VALINT("&H" + MID$(g, 8 * t + 1, 8))
            FOR u = 0 TO 5
               tn(r + u) = v MOD 30
               v \= 30
            NEXT u
         NEXT t
         LOCATE 19, 4: PRINT "-"
      CASE "#T"
         bpm = VALINT(MID$(g, 3))
         IF bpm < 1 THEN bpm = 1
      END SELECT
   LOOP
   st XOR= 1

   CLOSE 1
loadp = -1
END IF
END FUNCTION

SUB midiout (wr AS INTEGER)
STATIC bi(3) AS INTEGER, msg(3) AS INTEGER
STATIC fl AS INTEGER, hnd AS INTEGER, sta AS INTEGER
DIM chnk AS INTEGER, k AS INTEGER, os AS INTEGER
DIM r AS INTEGER, s AS INTEGER, t AS INTEGER
'
IF NOT fl THEN
   scale 0
   FOR t = 0 TO 11 '                  bias
      READ bi(t AND 3)
   NEXT t: fl = -1
   sta = 0: st = 0

   IF midiOpen(hnd, 0, 0, 0, 0) THEN
      LOCATE 21, 2
      PRINT "error opening MIDI device "
      SLEEP: END
   ELSE
      FOR t = 0 TO 15 '               initialize midi
         READ msg(0)
         midiMsg hnd, msg(0)
      NEXT t
      RESTORE
      FOR t = 0 TO 3
         msg(t) = 160 + t
      NEXT t
   END IF
END IF


   IF sta XOR st THEN '               map change
      sta = st
      FOR t = 0 TO 3
         msg(t) AND= NOT 16
         midiMsg hnd, msg(t)
      NEXT t
   END IF

   IF wr THEN
      FOR t = 0 TO 3
         os = vsp * t '               segment offset
         r = (msg(t) AND 16) = 16
         s = c(os MOD mx, sw) = 1

         IF r XOR s THEN '            flipped statusbit
            IF s THEN
               chnk = 0
               FOR k = os + 11 TO os + 1 STEP -1
                  chnk = chnk SHL 1 OR c(k MOD mx, sw)
               NEXT k
               r = bi(t) + tn((chnk AND 31) MOD 20)
               s = 44 + chnk SHR 5
               msg(t) = 144 + t '     channel(t) on
               msg(t) OR= r SHL 8 '   note number
               msg(t) OR= s SHL 16 '  velocity
            ELSE
               msg(t) AND= NOT 16 '   note off
            END IF

            midiMsg hnd, msg(t)
         END IF
      NEXT t
   ELSE
      midClose hnd '                  close midi device
   END IF
END SUB

FUNCTION period (n AS INTEGER) AS INTEGER
DIM d(mx - 1, 1) AS INTEGER, k AS INTEGER
DIM p AS INTEGER, s AS INTEGER
   FOR k = 0 TO mx - 1
      d(k, 0) = c(k, 1 - sw) '        store phase space
      d(k, 1) = c(k, sw)
   NEXT k

   p = 0: DO
      evaluate '                      cycle
      p += 1
      FOR k = 0 TO mx - 1
         s = c(k, 1 - sw) XOR d(k, 0)
         s OR= c(k, sw) XOR d(k, 1) ' compare
         IF s THEN EXIT FOR
      NEXT k
      IF p >= n THEN p = 0: s = 0 '   fail
   LOOP WHILE s

period = p
END FUNCTION

SUB populate (gen AS INTEGER)
STATIC fl AS INTEGER, m AS INTEGER
DIM k AS INTEGER, r AS INTEGER, s AS INTEGER
DIM t AS INTEGER, d AS SINGLE, dt AS SINGLE
'
IF (gen = 0) OR (m XOR mx) THEN
   IF NOT fl THEN
      SCREEN MODE: fl = -1 '          initialize gfx
      PALETTE 0, &H282828
      PALETTE 1, &H140000
      PALETTE 2, &H1529
      PALETTE 3, &H23000A
      PALETTE 4, &H2330
      PALETTE 5, &H0: COLOR 5
      r = 1 + dW * .25 * .618
      s = r + dW * .75 - 1
      VIEW (r, 1)-(s, dH - 2), , 5

      sw = 0: rndstat
      inrule (STR$(RND * 256) + "e") 'random elementary rule
      LOCATE 3, 2: PRINT "rule nr."
      LOCATE 4, 4: PRINT decrule$
      LOCATE 6, 2: PRINT "window"
      vsp = INT(mx * .251)
      LOCATE 9, 2: PRINT "space"
      LOCATE 10, 3: PRINT vsp
      LOCATE 12, 2: PRINT "generation"
      LOCATE 15, 2: PRINT "tempo"
      LOCATE 16, 3: PRINT bpm
      LOCATE 18, 2: PRINT "mode"
   END IF

   m = mx: d = mx - .5
   WINDOW SCREEN (-.5, -.5)-(d, d)
   LINE (-1, -1)-(mx, mx), 1, BF '    clear viewport
   FOR t = 0 TO mx
      dt = t - .5 '                   draw grid
      LINE (-.5, dt)-(d, dt), 5
      LINE (dt, -.5)-(dt, d), 5
   NEXT t
   LOCATE 7, 3: PRINT mx; " "
END IF
   LOCATE 13, 3: PRINT gen; " ";

   t = gen MOD mx '                   timestep
   FOR k = 0 TO mx - 1
      PAINT (k, t), c(k, sw) + 1, 5 ' paint cells
   NEXT k
   FOR s = 0 TO 3
      k = (vsp * s) MOD mx
      PAINT (k, t), c(k, sw) + 3, 5 ' singin' columns
   NEXT s
END SUB

SUB rndcell
DIM k AS INTEGER
   FOR k = 0 TO mx - 1
      c(k, 0) = 0: c(k, 1) = 0 '      clear all
   NEXT k
   k = mx SHR 1: st XOR= 1
   c(k, 0) = 1: c(k, 1) = 1 '         single alive cell
END SUB

SUB rndstat
DIM k AS INTEGER, s AS INTEGER, t AS INTEGER
   s = CINT(mx * .4)
   FOR k = 0 TO mx - 1
      IF k < s THEN
         c(k, 0) = 1: c(k, 1) = 1 '   fixed weight,
      ELSE
         c(k, 0) = 0: c(k, 1) = 0
      END IF
   NEXT k
   FOR s = 0 TO 1
      FOR k = 0 TO mx - 1
         t = INT(RND * mx) '          random distribution
         SWAP c(k, s), c(t, s)
      NEXT n
   NEXT s
   st XOR= 1
END SUB

SUB roll (fl AS INTEGER)
DIM k AS INTEGER, s AS INTEGER, t AS INTEGER
   FOR s = 0 TO 1
      IF fl < 0 THEN
         t = c(mx - 1, s) '           rotate right,
         FOR k = mx - 1 TO 1 STEP -1
            c(k, s) = c(k - 1, s)
         NEXT k
         c(0, s) = t
      ELSE
         t = c(0, s) '                left
         FOR k = 0 TO mx - 2
            c(k, s) = c(k + 1, s)
         NEXT k
         c(mx - 1, s) = t
      END IF
   NEXT s
   st XOR= 1
END SUB

SUB savep (n AS INTEGER)
DIM g AS STRING, h AS STRING, fl AS INTEGER, r AS INTEGER, s AS INTEGER
DIM t AS INTEGER, tx AS INTEGER, u AS INTEGER, v AS INTEGER
IF n < 0 OR n > 999 THEN n = 0
g = ".\piece000.par": h = STR$(n)
MID$(g, 11 - LEN(h)) = h

OPEN g FOR OUTPUT AS #1
   PRINT #1, "#D saved by CApieces.bas"
   PRINT #1, "#R "; decrule$
   PRINT #1, "#W"; mx
   tx = (mx - 1) SHR 3
   fl = sw XOR 1
   FOR s = 0 TO 1 '                   states t-1 and t
      g = STRING$(2 * tx + 2, "0")
      FOR t = 0 TO tx
         v = 0: r = t SHL 3
         FOR u = 7 TO 0 STEP -1
            v = v SHL 1 OR c(r + u, fl)
         NEXT u
         h = HEX$(v)
         MID$(g, 2 * t + 3 - LEN(h)) = h
      NEXT t: fl XOR= 1
      PRINT #1, "#S "; g
   NEXT s
   PRINT #1, "#V"; vsp
   g = STRING$(32, "0")
   FOR t = 0 TO 3 '                   shuffled notes
      v = 0: r = t * 6
      FOR u = 5 TO 0 STEP -1
         v = v * 30 + tn(r + u)
      NEXT u
      h = HEX$(v)
      MID$(g, 8 * t + 9 - LEN(h)) = h
   NEXT t
   PRINT #1, "#N "; g
   PRINT #1, "#T"; bpm
CLOSE 1
END SUB

SUB scale (n AS INTEGER)
DIM d(7) AS INTEGER, k AS INTEGER
DIM t AS INTEGER, u AS INTEGER
   FOR t = 0 TO 7
      READ d(t) '                     steps
   NEXT t
   RESTORE
   n AND= 7: st XOR= 1
   LOCATE 19, 3: PRINT 1 + n

   FOR k = 1 TO n '                   rotate
      u = d(0)
      FOR t = 0 TO 6
         d(t) = d(t + 1)
      NEXT t
      d(7) = u
   NEXT k

   tn(0) = 0
   FOR t = 0 TO 18 '                  note numbers
      tn(t + 1) = tn(t) + d(t AND 7)
   NEXT t
END SUB

SourceForge.net logo