Fastbreeder is essentially a 4 button synth. The idea is to grow code by choosing from a range of automatically generated variations of functions, you don't have to know how they work, but each function creates a sound which can be selected by you. The following generation is then created containing mutants of your chosen sound. You can refine and develop the sound just by auditioning and choosing the best one each time.
The interface shows you the code graphically, as a tree. These trees are attempts to clearly represent code structure, giving you some feedback on what is being evolved. Conventional listings are not that informative with GP (genetic programming), as deeply functional code such as this is hard to read linearly (see below).
Lets just say I haven't been using it to evolve the perfect piano sound. A harvest of sounds, with source code for further evolution.
sudo scons install
Using fastbreeder is extremely simple, and is partly designed for live use (although I haven't been brave enough to play it through a PA yet, let me know if you do!). There are no mappings for control other than playing the sounds, although osc commands could be read to provide terminal data for the functions, I haven't implemented it yet.
At startup, the interface presents 4 random functions, the user can select one with the left mouse button which sends it to the audio synth for playing, and causes the 3 other functions to be overwritten by mutated versions of the chosen function. The right mouse button auditions the selected function without changing the other functions, and middle mouse button randomises all the functions again.
Fastbreeder consists of two programs - one, written in python, does all the GP, builds the interface and supplies strings of code (in a lisp like format) to the second program - written in C++ which creates a synthesis graph from the code description, and generates the audio in realtime. As a side note, the server interprets the code from a single /synth OSC command, so it is possible to use on it's own as a very simple synth with live coding possibilities. Again, I haven't tried this yet, let me know if you do.
In a system such as this, it is tempting to provide the program with lots of specialist atomic operators, but I've tried to remain purist in my approach with fastbreeder, and keep the emphasis on the evolution of the code. These are the only operators it supports at this time:
There are another type of atomic operators which take no arguments, and as such are leaf nodes of the code tree:
Internally, the code used to pass evolved synth descriptions to the audio engine is similar to lisp, this is due to the fact it was easy for me to write a parser for, and it's very suitable for this kind of functional code.
(sin (* time 440 ) )This is about as simple as it gets, and produces a sine tone at 440 hz. As you generate interesting sounds, you are able to save the code to text files to modify, or reuse later on, for further evolution.
This is an example of a generated function:
(- (* (* (/ (/ (/ 713.662104415 445.034158546 ) (- 490.692993367 559.51504981 ) ) (* time 660.128287744 ) ) (% 590.971081136 (* (- 23.1423968616 785.746653358 ) time ) ) ) (/ (/ (* (sin 226.663113211 ) (* 245.756462957 409.931439243 ) ) (* (* 316.450983547 935.411202816 ) (/ 516.391765424 836.037528668 ) ) ) (% 385.016263895 (sin (* 427.285664609 840.056760839 ) ) ) ) ) (/ (+ (+ (+ (- 642.220260107 364.11006353 ) time ) time ) (% (sin (cos 599.645167648 ) ) time ) ) (sin (- (cos time ) 999.4616109 ) ) ) )
Good job you don't need to understand them, huh :)
Genetic programming is concerned with the evolution of tree structures (describing code). For more information on the current thinking in this field, see the main GP website. Currently fastbreeder is restricted to simple mutation of existing trees - no crossover between parent trees has been implemented yet, and only a small amount of mutational operations.
Pick a random function node, and copy it to another random function node. This duplicates subtree that comprised the function's arguments. It is also possible to create a recursive step, as a function can duplicate a function tree within itself. Duplication also causes the terminal nodes to be randomised, really this should be a seperate mutation operation.
Pick a random function node, and set it to a random terminal. This removes the entire subtree that comprised the function's arguments. This probably isn't needed, as duplication can cause similar effects, but it keeps code size down.