«first  …23 24 25 26 27 28 29 last»

Work with Mark: Genetics

2006-09-25 15:23:13 research, supercollider

I also spent time at UoW learning about genetic algorithms and genetic programming. Mainly from John H Holland's books and Karl Sims' papers. I found it all very interesting and inspiring and again I got great help and input from Rob Saunders.

One of our ideas was to construct synthesis networks from parts of our agents' genomes i.e. to have the phenomes be actual synths that would synthesise sound in realtime. The first problem to tackle was a really hard one. How to translate the genome - in the form of an array of floats - into a valid SuperCollider synth definition?

Of course, there are millions of ways to do this translation. I came up with the RedGAPhenome class which works with only binary operators, control and audio unit generators. Unfortunately, there can be no effects or modifier units. On the other hand, the class is fairy flexible and it can deal with genomes of any length (>=4). One can customise which operators and generators to use and specify ranges for their arguments. One can also opt for the topology of the synthesis network (more nested or more flat).

There is no randomness involved in the translation, so each gene should produce the exact same SynthDef. Of course, generators involving noise, chaos and such might make the output sound slightly different each time but the synthesis network should be the same.

This class produces a fantastic range of weird synths with odd synthesis techniques, and it is useful just like a synth creation machine on its own. Here are some generated synths... n_noises, n_fmsynths, and corresponding 5sec audio excerpts are attached below.

Then, after the struggle with the phenome translation, the code for the actual genetic algorithms was easy to write. The genome and its fitness are kept in instances of a class called RedGAGenome, and the cross-breeding and mutation are performed by the class RedGA. There are a couple of different breeding methods but I found the multi-point crossover one to give the generally best results. All the above classes and their respective help files and examples are available on the page: /code/sc/#classes. And there are many more automatically generated synths in the attached krazysynths+gui.scd example below.

I also made a couple of fun example applications stemming from this. One is a six-voice sequencer where you can breed synths, patterns and envelopes. It is attached as 'growing soundsBreedPatternEnv.scd' below. (Note that the timing is a bit shaky. I really should rewrite it to run on the TempoClock instead of the AppClock.)

growing_soundsBreedPatternEnv screenshot

Ref articles:

Ref books:

  • John H. Holland - Hidden Order: How Adaptation Builds Complexity
  • Melanie Mitchell - An introduction to Genetic Algorithms
  • Richard Dawkins - The Blind Watchmaker




  • 101128: growing_soundsBreedPatternEnv.scd file updated, also see the post /f0blog/growing-sounds/.
  • 171229: converted some rtf files to scd and made the GUI run on latest SuperCollider (Qt)

Work with Mark: iStreet - Online

2006-09-20 19:24:37 research

Taking the Intelligent Street project further, Mark d'Inverno wanted me to try to get it online. The idea was to let people surf to a webpage, send commands to a running iStreet system and hopefully collaborate with other online users to compose music. Just like in the original SMS-version of the piece, everybody could make changes to the same music and the result would be streamed back to all the online users.

The first prototype was easy to get up and running. We decided to use Processing and write a Java applet for the web user-interface and then stream the audio back with Shoutcast. The users would listen to the music through Itunes, WinAmp or some similar program. This, of course, introduced quite a delay before they could actually hear their changes to the music. But that was not all too bad as we had designed the original iStreet in a way that latency was part of the user experience :-)

(The commands sent there via SMS took approx 30 seconds to reach us from the Vodafone server and that could not be sped up. The users were given no instant control over the music - rather they could nudge it in some direction with their commands.)

So our internet radio/Shoutcast solution worked just fine and we had it up and running for a short while from Mark's house in London.

That was a total homebrew solution of course. We wanted it to handle a lot of visitors, deal with some hopefully high traffic, be more stable, permanent and not run on an ADSL connection.

So at UoW we got access to a OSX server cluster and I started to plan how to install SuperCollider, a webserver and iStreet on that. Little did I know about servers, network and security and I had to learn SSH and Emacs to get somewhere. Rob Saunders helped me a lot here.

Then there were some major obstacles. First of all the cluster didn't have any window manager installed - not even X11. I spent many days getting SuperCollider and Stefan Kersten's Emacs interface for sclang called scel to compile.

We also had some minor issues with starting the webserver and punching hole in the university firewall etc. but the major problem turned out to be to get the audio streaming going. I didn't have root access and wasn't allowed to install jack on the cluster. To stream, I needed a Shoutcast client and some way to get audio to that from SuperCollider. I did find OSX programs that could have worked but none would run windowless on the console. So stuck.

The only solution was to write my own streaming mechanism. The resulting SuperCollider class for segmenting audio into MP3s is available here: /f0blog/work-with-mark-istreet-recording-mp3s-for-streaming/. A Java gateway handled the communication between SuperCollider and the Java applet that would stitch these files back together. (The Java gateway program also distributed all the other network data like the chat, checking online users, pending/playing commands etc. It used NetUtil by Sciss).

Unfortunately, I never got the streaming thing to run smoothly. Nasty hickups in the sound made it impossible to listen to. The hiccups were probably partly due to my crappy coding but I think the main error was in the ESS library for Processing. Either ESS (releases 1 and 2) can't do asynchronous loading or Java is just too slow to load MP3s without dropping audio playback. Very annoying.

After that defeat, I also spent time with flash and did a little player there that could load and play back MP3s smoothly. With the help from my flash expert friend Abe we also could talk to the flash thing from my Java applet via JavaScript. But time ran out and this would have been a too complicated system anyway.

So the iStreet never made it online. But again I learned a lot about networks, Unix, Java and some tools got developed in the process. RedGUI - a set of user-interface classes for processing, ISRecord and ISGateway for SuperCollider, and the ISgateway.java.

Screenshot of the Java applet running iStreet online...

istreet online screenshot

Work with Mark: iStreet - Recording MP3s for Streaming

2006-09-20 14:52:04 research, supercollider

Mark d'Inverno wanted to see the Intelligent Street installation gain new live online. So for streaming the sound from iStreet over the internet, I wrote a class for SuperCollider called ISRecord. It basically records sound to disk in small MP3 segments. So any sound SuperCollider produces will be spliced into many short MP3 files that could later be sent as small packages over the internet.

The technique is to continuously save the sound into one of two buffers. When one buffer is filled, the recording swaps and continues in the other. The buffer that just got filled is saved to disk and conversion to MP3 is started. This swap-and-write-to-disk cycle should have no problems keeping up realtime recording. But as the MP3 conversion takes a little bit of extra time - depending on the quality and segmentation size etc, there is a callback message from the MP3 converter that evaluates a user-defined segAction function when the conversion is finished. Thereby one can notify other programs when the MP3 file is ready to be used.

There is also a cycle parameter that controls how many MP3 segments to save before starting to overwrite earlier ones. This is needed to not totally flood the harddrive with MP3s.

The actual MP3 conversion is done using LAME and CNMAT's sendOSC is also needed for LAME-to-SC communication.

Attached is the recorder class plus a help file.


Work with Mark: iStreet - OS X

2006-09-19 01:04:52 research

At UoW I also rewrote an old installation called The Intelligent Street. Mark d'Inverno and I had worked together on this one earlier and we now wanted it to run on a modern computer (OSX). We also wanted to redesign it a bit and make it into a standalone application.

The original Intelligent Street was a transnational sound installation were users could compose music using mobile phones and SMS. That, in turn, was an extended version of a yet older installation called 'The Street' by John Eacott. This totally reworked 'intelligent' version was premiered in November 2003 and realised as a joined effort between the Ambigence group (j.eacott, m.d'inverno, h.lörstad, f.rougier, f.olofsson) and the Sonic studio at the Interactive Institute in Piteå (way up in northern Sweden).

For the new OSX version, we dropped SMS as the only user interface and also removed the direct video+sound links between UK-SE that were part of the old setup. Except for that, the plan was to move it straight over to SuperCollider server (SC3) and rather spend time on polishing the overall sound and music.

I roughly estimated it would take just a few days to do the actual port. Most of the old code was written in SuperCollider version 2 (Mac OS9) and the generative music parts were done using SC2's patterns and Crucial libraries. So that code, I thought, would be pretty much forward compatible with our targeted SuperCollider 3. But sigh - it turned out that I had to rewrite it completely from scratch. The 'smart' tweaks and optimisations I had done in the old SC2 version in combination with the complexity of the engine made it necessary to redesign the thing bottom up. Even the generative patterns parts. Last I also dropped Crucial library for the synthesised instruments and did it all in bare-bone SC3.

But I guess it was worth the extra weeks of work. In the end, the system as a whole became more robust and better sounding. And standalone not to mention so hopefully it will survive a few years longer.

But I can also think of more creative work than rewriting old code. I've been doing that quite a lot recently. It feels like these old installations I've worked on earlier comes back to haunt me at regular intervals. And there is more and more of them for each year :-)

Proof: Intelligent Street running happily under OSX...

iStreet OSX

Work with Mark: Shadowplay

2006-09-18 23:16:23 research


Yet another system Mark d'Inverno and I worked on but never finished had the working title 'shadowplay'. We had this idea about an audiovisual installation where people's limbs (or outlines of bodies) would represent grid worlds. Agents would live in these worlds and evolve differently depending on things like limb size, limb movement over time, limb shape and limb position. The agents would make different music/sounds depending on the world they live in. A limb world could be thought of as a musical part in a score. The worlds would sound simultaneous but panned to different speakers to help interaction.

The visitors would see the outline of their bodies projected on a big screen together with the agents represented visually in this picture as tiny dots. Hopefully, people could then hear the agents that got caught or breed inside their own limbs. We hoped to active a very direct feeling of caressing and breeding your own sounding agents.

There were plans for multi-user interaction: if different limbs/outlines touched (e.g. users shaking hands), agents could migrate from one world to another. There they would inject new genes in the population, inflicting the sound, maybe die or take over totally. Though to keep agents within the worlds they were made to bounce off outlines. But one could shake off agents by moving quickly or just leave the area. These 'lost' agents would then starve to death if not adopted by other users.


The whole thing was written in Processing and SuperCollider. Processing did the video and graphics: getting the DV input stream, doing blob tracking (using the 3rd party library blob detection) and drawing the agents and lines for the limbs. SuperCollider handled rest: the sound synthesis, the genetics, agent state and behaviours, keeping track of the worlds etc. We used a slightly modified version of our A4 agent framework I wrote about in the following post: /f0blog/work-with-mark-bottom-up-approach/.

The two programs communicated via a network (OSC) and would ideally run on different machines.

I had major problems with programming. The math was hairy and all the features were very taxing on the CPU. We never got further than a rough implementation.

shadowplay screenshot 1 shadowplay screenshot 2 shadowplay screenshot 3

Work with Mark: Array Primitives and Benchmarking the Framework

2006-09-18 13:40:07 research, supercollider

I spent a lot of time benchmarking the code for the agents in the different versions of the multi-agent frameworks I posted about here earlier. The best performance boost was (of course) when I ported some vital parts of the SuperCollider code to C. So, for example, one CPU-intensive task that the agents had to do a lot was to find out about their surroundings. And another 'heavy' task was to calculate the distance to other objects. Each operation wasn't very demanding on its own, but when hundreds of agents would do this at the same time, we really needed the speed of the C primitives.

Below is the C code I came up with. It replaces some of the computational heavy parts in the Surroundings and ALoaction SuperCollider classes. To try them out you'd need to download the SuperCollider source code from github.com/supercollider/supercollider, add my code to the file PyrArrayPrimitives.cpp and then recompile the whole application. To use these primitives you also need to edit the file extArrayedCollection.sc from the A4.zip package posted here earlier.

One issue we then had was to distribute this 'hack'. SuperCollider doesn't have an API for adding extensions like this to the language (but there's a nice plugin architecture for the server). So I had to build dedicated SuperCollider applications including this speed hack.

int prArrayAsALocationIndex(struct VMGlobals *g, int numArgsPushed)
    PyrSlot *a, *b;
    PyrObject *obj;
    int size, i;
    double asIndex, w, worldSize;
    a = g->sp - 1;
    b = g->sp;
    worldSize = b->ui;
    obj = a->uo;
    size = obj->size;
    asIndex= 0;
    for (i=0; i<size; ++i) {
        getIndexedDouble(obj, i, &w);
        asIndex= asIndex+(pow(worldSize, i)*w);
    SetFloat(a, asIndex);
    return errNone;
int prArrayAsALocationRoundedIndex(struct VMGlobals *g, int numArgsPushed)
    PyrSlot *a, *b;
    PyrObject *obj;
    int size, i;
    double asIndex, w, worldSize;
    a = g->sp - 1;
    b = g->sp;
    worldSize = b->ui;
    obj = a->uo;
    size = obj->size;
    asIndex= 0;
    for (i=0; i<size; ++i) {
        getIndexedDouble(obj, i, &w);
        w= sc_round(w, 1.0);
        w= sc_clip(w, 0, worldSize-1);
        asIndex= asIndex+(pow(worldSize, i)*w);
    SetFloat(a, asIndex);
    return errNone;
int prArrayDistance(struct VMGlobals *g, int numArgsPushed)
    PyrSlot *a, *b;
    PyrObject *obj1, *obj2;
    int size, i;
    double w1, w2, distance;
    a = g->sp - 1;
    b = g->sp;
    if (b->utag != tagObj || a->uo->classptr != b->uo->classptr) return errWrongType;
    obj1 = a->uo;
    obj2 = b->uo;
    size = obj1->size;
    distance= 0;
    for (i=0; i<size; ++i) {
        getIndexedDouble(obj1, i, &w1);
        getIndexedDouble(obj2, sc_mod(i, size), &w2);
        distance= distance+pow(w2-w1, 2);
    SetFloat(a, fabs(sqrt(distance)));
    return errNone;
int prArraySurroundings(struct VMGlobals *g, int numArgsPushed)
    PyrSlot *a, *b, *c, *d, *areaArraySlots, *indexArraySlots, *outArraySlots;
    PyrObject *obj, *areaArray, *indexArray, *outArray;
    int areaSize, outSize, i, j, worldDim, area;
    double w;
    a = g->sp - 3;  //list
    b = g->sp - 2;  //worldDim
    c = g->sp - 1;  //area - as float possible later?
    d = g->sp;  //boolean - exclude/include fix later
    if (b->utag != tagInt) return errWrongType;
    if (c->utag != tagInt) return errWrongType;
    if (d->utag != tagTrue && d->utag != tagFalse) return errWrongType;
    obj = a->uo;
    worldDim = b->ui;
    area = c->ui;
    areaSize = area*2+1;
    indexArray = newPyrArray(g->gc, worldDim, 0, true);
    indexArraySlots = indexArray->slots;
    indexArray->size = worldDim;
    if (IsTrue(d)) {  //--build index array excluding
        areaArray = newPyrArray(g->gc, areaSize-1, 0, true);
        areaArraySlots = areaArray->slots;
        areaArray->size = areaSize-1;
        int j = 0;
        for (i=0; i<areaSize-1; ++i) {
            int temp = 0-area+i;
            if (temp==0) {j++;}
            areaArraySlots[i].ucopy = temp+j;
        outSize = pow(areaSize, worldDim)-1;
    } else {  //--build index array including
        areaArray = newPyrArray(g->gc, areaSize, 0, true);
        areaArraySlots = areaArray->slots;
        areaArray->size = areaSize;
        for (i=0; i<areaSize; ++i) {
            areaArraySlots[i].ucopy = 0-area+i;
        outSize = pow(areaSize, worldDim);
    for (i=0; i<worldDim; ++i) {
        SetObject(indexArraySlots+i, areaArray);
    //indexArray is here... [[-1, 0, 1]] or [[-1, 0, 1], [-1, 0, 1]] etc. for area=1
    //or [[-2, -1, 0, 1, 2]] or [[-2, -1, 0, 1, 2], [-2, -1, 0, 1, 2]] etc. for area=2
    //--all tuples
    outArray = newPyrArray(g->gc, outSize*sizeof(PyrObject), 0, true);
    outArraySlots = outArray->slots;
    outArray->size = outSize;
    for (i=0; i<outSize; ++i) {
        int k = i;
        PyrObject *tempArray = newPyrArray(g->gc, worldDim, 0, true);
        PyrSlot *tempArraySlots = tempArray->slots;
        tempArray->size = worldDim;
        for (j=worldDim-1; j>=0; --j) {
            tempArraySlots[j].ucopy = areaArraySlots[k%areaSize].ucopy;
            getIndexedDouble(obj, j, &w);
            tempArraySlots[j].ucopy += w;
            k /= areaSize;
        SetObject(outArraySlots+i, tempArray);
    a->uo = outArray;
    return errNone;

Edit extArrayedCollection.sc (from A4.zip posted in an earlier blog entry) to look like this...

+ ArrayedCollection {
    asALocationIndex {|size|
    asALocationRoundedIndex {|size|
    distance {|list|
    surroundings {|dimensions= 2, area= 1, exclude= true|

SuperCollider code for benchmarking...

    var size= 100, cSize= 2, rule= 30;
    var world, agents, y= 0, dict;
    dict= ();  /*lookup dictionary for rules*/
    8.do{|i| dict.put(i.asBinaryDigits(3).join.asSymbol, rule.asBinaryDigits[7-i])};
    ACell.rules= dict;
    world= APattern(size);  /*create 1d world*/
    size.do{|i| ACell(ALocation(world, [i]))};  /*fill up 1d grid with agents*/
    world.get(ALocation(world, [(size/2).round])).value= 1;  /*middle agent value=1 as init*/
    agents= world.items;
    while({y<size}, {
        /*here update.  first all agents.sense then all agents.act*/
        agents.do{|a| a.sense};
        /*agents.do{|a| a.location.list_([(size/2).round])};*/
        /*agents.do{|a| a.location= ALocation(a.location.world, [(size/2).round])};*/
        agents.do{|a| a.act};
        y= y+1;

Work with Mark: Bottom-up Approach

2006-09-18 12:57:30 research, supercollider

After some time Mark d'Inverno and I shifted focus and decided to simplify our ideas. We agreed to work more from a bottom-up approach - letting the agents live within a grid world and visualise their behaviours. Other people have been doing quite some work in this area before, but not particularly many of them have been incorporating sound and music. So we had literature and examples to study and people to ask. It was of great help and I learned a lot about designing multi-agent systems from analysing, for example, Jon McCormack's nice Eden.

So starting out writing our own system, I did a set of classes for handling agents running around in a grid world of 1-3 dimensions. All agents were very simple-minded. They were visually represented with just dots and oh, they could bleep too.

Setting up simple scenarios for these classes helped to pinpoint different system models. It also showed my biggest problems coding this usually boiled down to in which order to do things. The model I tried in turn to 'model' was suggested by Rob Saunders in an article called 'Smash, bam and cell', in were all agents first sense their surroundings and then act. But I constantly had to restructure the code and design. This was harder than I had thought and I think I never came up with an all-around solution.

One example scenario we came up with was the runaway test. It is very simple but can help trying out different designs. It works something like this... Imagine a grid world of say 2 dimensions (we also coded this in 1 and 3D). Agents move about at random speed and direction. If an agent encounters another one blocking its next step, it turns around 180 degrees and flees i.e. moving away in the opposite direction. So far the sense/act cycle is simple: for every update (world tick) it first sense, then acts. But what happens if there's another agent blocking the escape route? So the agents really need to first sense, then if something is ahead, act and turn around 180, sense again and decide if it is possible to flee. Here it'll sense within the act method and that clutters the design. The better solution would probably be to let the agent just turn 180 and wait to flee until the next tick. But perhaps it could also sense behind itself in the first sense round and pause if both escape routes are blocked. There are many possible solutions and creating these small test scenarios helped me to generalise my classes. We also tried the classes by coding the test scenarios as discrete and continuous i.e. if the world was a rigid grid in which the agents only were allowed to move stepwise, or if the world allowed them to move about more smoothly in non-integer directions and speeds.

The SuperCollider code for version 4, including test scenarios and small examples is attached at the bottom of this post and below is some text trying to describe the classes in more detail.

Also, see these QuickTime movies of some test scenarios...

  • one-dimensional continuous world
  • two-dimensional continuous world
  • one-dimensional discrete world
  • one-dimensional discrete world
  • one-dimensional discrete world
  • one-dimensional discrete world
  • two-dimensional discrete world

A4 description

There are 3 basic classes. ALocation, AWorld and AnItem. I first describe them and their immediate subclasses. Then AnAgent, AProxyAgent and some subclasses of AWorld. Then I explain a few of the classes used in the test/example programs. Last I write a little about the main loop.

May look at the files A4.sc and A4subs.sc for completion.



A place within a world.

.new takes 2 arguments: world and list

Instance variable world is any (sub)class of AWorld. Can be get (i.e. accessed from outside).

Instance variable list is a list of coordinates. Can be get and set. Example: [10] for x=10 in an 1 dimensional world. [10, 20, 30] for x, y, z in a 3D world. The length of the list must correspond to the number of dimensions in the world.


Locations can be compared with the == method. It takes another location as an argument. A location is equal to another if they exist in the same world and have the same list of coordinates.

The != method is the negation of ==.

Distance between 2 locations can be found with the distance method. It'll return the shortest distance between locations in any dimension.

With the at method, one can query single dimensions. E.g. in a 2D world, location.at(0) will return which column and location.at(1) will return row. The argument is really just index in list above.

The surroundingLocations method. With the arguments exclude(boolean) and area(int) this returns a list of new location objects. This is used for collecting locations to be searched for neighbours. If exclude argument flag is false, this (i.e. the current) location will be counted and included in the list. The locations returned are all positioned next to this location in a cube-like way, covering an area of size: area steps away. To put it in another way: with an area of 1, only directly adjacent locations are returned. An area of 2 gives adjacent and their adjacent locations (as a set of ALocation objects) and so on.

So in a 1D world a location at (0) sent the message .surroundingLocations(false, 1) will give us [loc[-1], loc[0], loc[1]]. And likewise in a 2D world a location at (4, 5) sent the message .surroundingLocations(false, 1) will return [loc[3, 4], loc[3, 5], loc[3, 6], loc[4, 4], loc[4, 5], loc[4, 6], loc[5, 4], loc[5, 5], loc[5, 6]]. Here's the code that resembles this: ALocation(AWorld(2, 10), [4, 5]).surroundingLocations(false, 1). Last example: a location within a 3D world asked to return its surroundings with an area of 3 like this: ALocation(AWorld(3, 100), [40, 50]).surroundingLocations(false, 3).size will return a list of 343 unique locations.


When a location object is created it check its world's size and wrap around borders (by doing modulo(size) on the coordinates in the list).

The location class expects the world to be of uniform size in all dimensions.


  • 050712 - distance might need to go in a subclass if we do networked worlds - Rob's comment. How to calculate the distance between worlds?
  • 050712 - At Rob's suggestion: I'll try to rewrite this and the world classes using hashtable lookup. The matrix/location duality causes trouble keeping the same thing at two places.
  • 050712 - Naming needs to be improved. Specially AQLocation - what to call it?
  • 050726 - Removed the maxDimension and surroundMaxArea limitations and its class variable
  • 050726 - Now hashtable lookup + C primitives. Quite a lot faster overall and easier to keep things at the same place.


  • AQLocation - a quantified location. The coordinates for this class can be floting point but when it places itself in the matrix it rounds of to nearest integer.



A placeholder for items. Superclass for APattern, AGrid, ACube, BugWorld etc.

.new takes 3 arguments: dimensions, size and location

Instance variable dimensions is an integer specifying the number of dimensions for this world. Can be get.

Instance variable size will decide the size of 1 dimension. The world is then created with a uniform size in all dimensions. Can be get.

Instance variable location if defined, will place the world at a location. If left out - no parent. Can be get.


The clear method takes a location object as an argument and puts nil there.

With the remove method - argument: an item - you remove an item from this world.

With put you place an item in this world. Argument: item

The get method returns whatever item(s) is in a location. Argument: location

Method items returns a list of all items in this world.

neighbours - arguments: item, exclude and area. Returns a list of any items within an item's area (1=adjcent locations) including or excluding the item's own location. If no items nearby then empty list.

neighboursSparse is similar to neighbours above (same arguments and function) but uses a different algorithm for finding nearby items. Where neighbours calculates locations around the item in question and then check these locations for any items, this method might be quicker in a sparse world. It looks through all items in the world and checks if they're nearby.

Running the update method goes through all items in this world, copies them to their own locations. This is to make sure all item's locations and the hashtable stays in sync.

save will write this world, its current settings and all its items and their settings to disk. This allows for backup of longrunning tasks or 'presets' to be loaded.




  • AWorld2 - remove and put methods are modified to allow for multiple items at the same location. AWorld can only do one item in once location. Hopefully, I can merge the two classes later.
  • ASmartWorld - is a world that can resolve location conflicts. There's a resolve method to be called after the sense cycle but before act. This goes through all items and if more than one intends to move to the same location, only let one move - others stay put and their request is ignored. (Comment: need to find a better name than ASmartWorld)
  • And many other subclasses. E.g. BugWorld, APattern, AGrid, ACube. Almost every test program has its own specialised class inheriting from these two subclasses.



The lowest level thing that exists in a world. Abstract superclass class for ARock, AMoss, AnAgent

.new takes 1 argument: location


The method remove will remove this item from its world.


The abstract init method is used by some subclasses for initialisation.


  • 050726 - is remove needed? Will the agent remove itself or will the world handle that eg if energy=0.


  • ARock - does nothing different. Just exists at a location
  • AMoss - has an energy that can be get/set.
  • AnAgent - is an abstract class. See below


Subclass of AnItem but is also an abstract class.

Makes sure the sense and act methods are there for all agents to follow.


  • Many. E.g. ACell, ARunaway, ABounce, Bug. Every test program has its own specialised class inheriting from this one. They all do sense and act in their own way.


Subclass of AnAgent. It allows replacing the sense and act methods while running.


When asked to sense and act, sense and act in this class will instead evaluate functions stored in the 2 class variables senseFunc and actFunc. These can be replaced and coded on the fly! So while the system is running, we can try out, completely rewrite or just slightly modify, behaviour for all agents. Their state is kept (individually) but behaviour changes.


  • This is unique to other frameworks I've seen so far. I'd like to explore more and hopefully, we can use it in practice too - not just as a convenience for developing. With this feature, it's easy to replace the rules on the fly.
  • Perhaps I redesign the whole framework to use proxies. So the AnItem class is really a placeholder (proxy) for anything. Then one can code whole agents with state and behaviour while running I think. And maybe proxy worlds too but I can't find a reason for that now.



A subclass of AWorld with 1 dimension.



A subclass of AWorld that has 2 dimensions.



A subclass of AWorld that has 3 dimensions.


used in A4_test1_cellautomata.scd


Subclass of AnAgent. It doesn't move and is used for cellular automata and game-of-life.

Instance variable value can be 0 or 1. Can be get/set.

There's also a rules class variable that contains a dictionary for rule lookup.


The sense method here collects and stores values from nearby neighbours (by asking the world for neighbours) including the cell's own value.

The act method set the cell's own value to what is returned from the rules dictionary.


used in A4_test2_gameoflife.scd


Subclass of ACell. Just implements different sense and act methods.


The sense method here is the same as ACell.sense but excludes the cell's own value.

act will first calculate the total sum of all neighbour's values and then do a lookup in the rules dictionary. The cell's own values is set to 0 or 1 depending on what the dictionary returns.


used in A4_test3_runaway1D.scd, A4_test4_runaway2D.scd and A4_test8_runaway3D.scd


A subclass of AnAgent that sense if something at next location and if so, bleep, turn around 90 and flee.

Instance variable direction is a list of directions in any dimension. In a 2D world: [0, 0] stand still, [-1, 0] go west, [1, 1] go northeast and so on. Can be get/set.

Instance variable freq decides which bleep frequency to play.


The sense method updates the 2 private nextLocation and nextPos instance variables to figure out where to go and if that location is taken.

Helper method clearAhead returns true if there's nothing in nextPos

getNextLocation returns a new location object at here + directionlist.

getNewDirection method turns directionlist around 90 degrees.

The move method sets this location to nextLocation

The play method will beep at a frequency. And pan the sound left/right depending on location.


1D - used in A4_test6_bounce1D.scd


Subclass of ARunaway. Implements getNextLocation and getNewDirection differently so that the agents bounce of each other rather than turn 90 degrees.

direction is here a vector of angle and degree.


  • ABounce2D and ABounce3D for vector math in other dimensions.

The main loop of the program is usually very simple. For the CellularAutomaton and GameOfLife examples it just draws a rectangle if the cell's value is 1, then call .sense on all agents and last call .act for all agents.

while {true}, {
    agents.do{|a| if(a.value==1, {a.paintRect})}
    agents.do{|a| a.sense};
    agents.do{|a| a.act};

Agents that move around (i.e. all other examples) need to resolve conflicts and update the world. Also they always draw themselves.

while {true}, {
    agents.do{|a| a.paintRect}
    agents.do{|a| a.sense};
    agents.do{|a| a.act};


  • 061017: bugfix to the distance method in extArrayedCollection.sc
  • 171228: new help file and scd instead of rtf

Work with Mark: Cellular Automata

2006-09-18 00:05:15 research, supercollider

Another thing I played around with while at UoW was cellular automata. Here's a simple one-dimensional CA class for SuperCollider... external link (or attached as zip below)

ca pen example 1 screenshot

And here is some more SuperCollider code I wrote to come to grips with CA...

/*cellular automata /redFrik*/
    var w, u, width= 400, height= 300, cellWidth= 1, cellHeight= 1;
    w= Window("ca - 1", Rect(128, 64, width, height), false);
    u= UserView(w, Rect(0, 0, width, height));
    u.background= Color.white;
    u.drawFunc= {
        var pat, dict, rule, ruleRand, y= 0;
        rule30= 30.asBinaryDigits;    // [0, 0, 0, 1, 1, 1, 1, 0];
        rule90= 90.asBinaryDigits;    // [0, 1, 0, 1, 1, 0, 1, 0];
        rule110= 110.asBinaryDigits;  // [0, 1, 1, 0, 1, 1, 1, 0];
        rule250= 250.asBinaryDigits;  // [1, 1, 1, 1, 1, 0, 1, 0];
        rule254= 254.asBinaryDigits;  // [1, 1, 1, 1, 1, 1, 1, 0];
        /*-- select rule here --*/
        //rule= 256.rand.postln;
        //rule= 90;
        rule= 30;
        pat= 0.dup((width/cellWidth).round);
        pat.put((pat.size/2).round, 1);
        dict= ();
        8.do{|i| dict.put(i.asBinaryDigits(3).join.asSymbol, rule.asBinaryDigits[7-i])};
        Pen.fillColor= Color.black;
        while({y*cellHeight<height}, {
            pat.do{|c, x|
                if(c==1, {
                    Pen.fillRect(Rect(x*cellWidth, y*cellHeight, cellWidth, cellHeight));
            pat= [0]++pat.slide(3, 1).clump(3).collect{|c|
            y= y+1;

ca pen example 2 screenshot

ca pen example 3 screenshot

More interesting than these simple examples are of course things like game-of-life.

ca game of life screenshot

Here's one implementation of GOL for SuperCollider...

//game of life /redFrik
    var envir, copy, neighbours, preset, rule, wrap;
    var w, u, width= 200, height= 200, rows= 50, cols= 50, cellWidth, cellHeight;
    w= Window("ca - 2 pen", Rect(128, 64, width, height), false);
    u= UserView(w, Rect(0, 0, width, height));
    u.background= Color.white;
    cellWidth= width/cols;
    cellHeight= height/rows;
    wrap= true;  //if borderless envir
    /*-- select rule here --*/
    //rule= #[[], [3]];
    //rule= #[[5, 6, 7, 8], [3, 5, 6, 7, 8]];
    //rule= #[[], [2]];  //rule "/2" seeds
    //rule= #[[], [2, 3, 4]];
    //rule= #[[1, 2, 3, 4, 5], [3]];
    //rule= #[[1, 2, 5], [3, 6]];
    //rule= #[[1, 3, 5, 7], [1, 3, 5, 7]];
    //rule= #[[1, 3, 5, 8], [3, 5, 7]];
    rule= #[[2, 3], [3]];  //rule "23/3" conway's life
    //rule= #[[2, 3], [3, 6]];  //rule "23/36" highlife
    //rule= #[[2, 3, 5, 6, 7, 8], [3, 6, 7, 8]];
    //rule= #[[2, 3, 5, 6, 7, 8], [3, 7, 8]];
    //rule= #[[2, 3, 8], [3, 5, 7]];
    //rule= #[[2, 4, 5], [3]];
    //rule= #[[2, 4, 5], [3, 6, 8]];
    //rule= #[[3, 4], [3, 4]];
    //rule= #[[3, 4, 6, 7, 8], [3, 6, 7, 8]];  //rule "34578/3678" day&night
    //rule= #[[4, 5, 6, 7], [3, 5, 6, 7, 8]];
    //rule= #[[4, 5, 6], [3, 5, 6, 7, 8]];
    //rule= #[[4, 5, 6, 7, 8], [3]];
    //rule= #[[5], [3, 4, 6]];
    neighbours= #[[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]];
    envir= Array2D(rows, cols);
    copy= Array2D(rows, cols);
    cols.do{|x| rows.do{|y| envir.put(x, y, 0)}};
    /*-- select preset here --*/
    //preset= #[[0, 0], [1, 0], [0, 1], [1, 1]]+(cols/2);  //block
    //preset= #[[0, 0], [1, 0], [2, 0]]+(cols/2);  //blinker
    //preset= #[[0, 0], [1, 0], [2, 0], [1, 1], [2, 1], [3, 1]]+(cols/2);  //toad
    //preset= #[[1, 0], [0, 1], [0, 2], [1, 2], [2, 2]]+(cols/2);  //glider
    //preset= #[[0, 0], [1, 0], [2, 0], [3, 0], [0, 1], [4, 1], [0, 2], [1, 3], [4, 3]]+(cols/2);  //lwss
    //preset= #[[1, 0], [5, 0], [6, 0], [7, 0], [0, 1], [1, 1], [6, 2]]+(cols/2);  //diehard
    //preset= #[[0, 0], [1, 0], [4, 0], [5, 0], [6, 0], [3, 1], [1, 2]]+(cols/2);  //acorn
    preset= #[[12, 0], [13, 0], [11, 1], [15, 1], [10, 2], [16, 2], [24, 2], [0, 3], [1, 3], [10, 3], [14, 3], [16, 3], [17, 3], [22, 3], [24, 3], [0, 4], [1, 4], [10, 4], [16, 4], [20, 4], [21, 4], [11, 5], [15, 5], [20, 5], [21, 5], [34, 5], [35, 5], [12, 6], [13, 6], [20, 6], [21, 6], [34, 6], [35, 6], [22, 7], [24, 7], [24, 8]]+(cols/4);  //gosper glider gun
    //preset= #[[0, 0], [2, 0], [2, 1], [4, 2], [4, 3], [6, 3], [4, 4], [6, 4], [7, 4], [6, 5]]+(cols/2);  //infinite1
    //preset= #[[0, 0], [2, 0], [4, 0], [1, 1], [2, 1], [4, 1], [3, 2], [4, 2], [0, 3], [0, 4], [1, 4], [2, 4], [4, 4]]+(cols/2);  //infinite2
    //preset= #[[0, 0], [1, 0], [2, 0], [3, 0], [4, 0], [5, 0], [6, 0], [7, 0], [9, 0], [10, 0], [11, 0], [12, 0], [13, 0], [17, 0], [18, 0], [19, 0], [26, 0], [27, 0], [28, 0], [29, 0], [30, 0], [31, 0], [32, 0], [34, 0], [35, 0], [36, 0], [37, 0], [38, 0]]+(cols/4);  //infinite3
    //preset= Array.fill(cols*rows, {[cols.rand, rows.rand]});
    preset.do{|point| envir.put(point[0], point[1], 1)};
    i= 0;
    u.drawFunc= {
        i= i+1;
        Pen.fillColor= Color.black;
                if(envir.at(x, y)==1, {
                    Pen.addRect(Rect(x*cellWidth, height-(y*cellHeight), cellWidth, cellHeight));
                var sum= 0;
                    var nX= x+point[0];
                    var nY= y+point[1];
                    if(wrap, {
                        sum= sum+envir.at(nX%cols, nY%rows);  //no borders
                    }, {
                        if((nX>=0)&&(nY>=0)&&(nX<cols)&&(nY<rows), {sum= sum+envir.at(nX, nY)});  //borders
                if(rule[1].includes(sum), {  //borne
                    copy.put(x, y, 1);
                }, {
                    if(rule[0].includes(sum), {  //lives on
                        copy.put(x, y, envir.at(x, y));
                    }, {  //dies
                        copy.put(x, y, 0);
        envir= copy.deepCopy;
    Routine({while{w.isClosed.not} {u.refresh; i.postln; (1/20).wait}}).play(AppClock);


  • 171228: converted rtf to scd and added help file

«first  …23 24 25 26 27 28 29 last»