«Cellular Automaton» by coreyker

on 10 Mar'15 07:48 in guialgorithmiccellular automaton

A supercollider implementation of algorithmic composition using cellular automaton (please see: http://www.earslap.com/page/otomata.html for my original inspiration). This code displays a grid of buttons, which can be clicked to generate new agents which behave according to a simple set of rules, that can lead to interesting emergent patterns:

  1. Initially move downward;
  2. If a boundary is encountered or if two agents collide, reverse direction;
  3. If two agents attempt to occupy the same cell, each agent should rotate 90 degrees to its left and continue moving

Sound is generated whenever an agent encounters a boundary, and different boundary locations correspond to different notes on a scale

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
(
s.waitForBoot({

var scale, width, height, n, space, w, cells, cellCreator, clearFunc;

// sound source
SynthDef(\blip, {|freq|
	var rel = TRand.kr(0.05,4,1);
	var s = SinOsc.ar(freq*[0.9,0.99,1,1.01,1.1], mul:[0.05,0.2,1,0.2,0.05]).mean;
	var e = EnvGen.ar(Env.perc(1e-4,rel,0.75), 1, doneAction:2) ** 4;
	Out.ar(0, s*e!2);
}).add;

s.sync;

scale = (12*[3,4,5,6] +.x Scale.egyptian.degrees.flatten).midicps;

// GUI
width = 600;
height = width;
n = 13;
space = width/n;

// make a new window
w = Window.new("automata", Rect(100,100,width,height));

// make empty data structure to hold program state
cells = [];

cellCreator = {|row, col, id|
	var x = ();
	x.id = id;
	x.pos = [row, col];
	x.dir = [0,1];

	x.updateCell = {|self|
		// reverse direction if at boundary
		if( self.pos[0]==n && self.dir[0]==1, {
			self.dir[0] = -1;
			Synth(\blip, [\freq, scale.wrapAt(self.pos[1]-1)]);
		});
		if( self.pos[1]==n && self.dir[1]==1, {
			self.dir[1] = -1;
			Synth(\blip, [\freq, scale.wrapAt(12*self.pos[0]-1)]);
		});
		if( self.pos[0]==1 && self.dir[0] == -1, {
			self.dir[0] = 1;
			Synth(\blip, [\freq, scale.wrapAt(24*self.pos[1]-1)]);
		});
		if( self.pos[1]==1 && self.dir[1] == -1, {
			self.dir[1] = 1;
			Synth(\blip, [\freq, scale.wrapAt(36*self.pos[0]-1)]);
		});

		// advance position
		self.pos = self.pos + self.dir;
	};

	x.changeDir = {|self|
		switch(self.dir,
			[1,0], {self.dir = [0,-1]},
			[-1,0], {self.dir = [0,1]},
			[0,1], {self.dir = [-1,0]},
			[0,-1], {self.dir = [1,0]}
		);
	};

	x;
};

clearFunc = {
	n.do{|i|
		n.do{|j|
			b[i][j].value=0;
		};
	};
};


// fill window with buttons
b =
n.collect{|i|
	n.collect{|j|
		Button(w, Rect(space*i, space*j, space, space))
		.states_([["", Color.black, Color.gray], ["", Color.black, Color.red]])
		.action_({|x|
			if(x.value == 1, {
				cells = cells ++ [cellCreator.value(i+1, j+1, cells.size)];
			})
		});
	}
};

w.drawFunc = Routine{
	loop{
		var ncells = cells.size;

		clearFunc.();

		// update cells
		cells.do{ |x|
			x.updateCell;
			b[x.pos[0]-1][x.pos[1]-1].value = 1;
		};

		// check for collision paths
		(ncells-1).do{|i|
			(ncells-i-1).do{|j|
				var k = j+i+1;
				if( (cells[i].pos == cells[k].pos) && (cells[i].dir == (-1*cells[k].dir)),
					{
						cells[i].changeDir;
						cells[k].changeDir;
					}
				)
			}
		};
		//"------------------".postln;
		0.yield;
	}
};

{ while { w.isClosed.not } { w.refresh; 0.125.wait; } }.fork(AppClock);

w.front;

}.fork(AppClock);
);

)
raw 2612 chars (focus & ctrl+a+c to copy)
reception
comments
p.dupuis user 10 Mar'15 23:59

Very nice!

Svdk Vedeka user 14 Nov'16 12:13

nice work

Kelley van Evert user 27 Nov'16 23:42

Really cool! I've put some 10+ agents in and am now happily listening to some nice patterns indeed :D