






test publication
Abstract
/*
this is a comment
*/
Class {
*getClass { ^class }
}
Dan pastes in his GAOD code. (The situation is slightly different since I'm on a PC at work rather than a Mac at home, and this is a "normal" wiki page rather than a "publication" wiki page.)
OK, test publication
/*
GAOD = Genetic Algorithms (to create your) Own Drone
(c) 2005 Dan Stowell. Licensed under the Gnu Public Licence (GPL).
This file contains the following classes:
* GaodInd - Represents an individual in a Genetic Algorithm
* GaodIsland - A simple "world" or "island" to contain a set of interacting individuals - handles culling etc
* GenomeToPhenome - Holds the methods for turning a row of numbers into a UGen graph
Example of usage:
---------------------------------------------------------
Server.local.boot;
// You may need to fiddle with the parameters in the next line to adapt to what your CPU can handle!
w = GaodIsland.new(12, delay:4, yearLength:15, genomeLength:10);
w.play;
w.modifyFitnesses(2); // Tell it that the currently-sounding ones are BAD
w.modifyFitnesses(0.5); // Tell it that the currently-sounding ones are GOOD
w.stopGently; // ...and relax
---------------------------------------------------------
For theoretical background about Genetic Algorithms, this book is excellent:
Peter J Bentley (ed.) 1999, "Evolutionary design by computers", ISBN 155860605X
*/
GaodInd {
var <>genome, // An array of floats between 0 and 1
<>defaultGenomeLength,
<>age,
// Fitness defaults to 1. To judge an individual as bad, increase it. Otherwise decrease it
<>fitness,
pcrossover,
pdelete,
pmutate,
pduplicate,
phenome, // Will hold the UGen graph
<curvol, // current volume goes up and down in a sine-like way,
// and only when high will fitness judgments apply
<volfreq, // This defines how fast/slow the thing fades
<>mul=1.0, // Typically you'd want to divide the amplitude by the number of individs that might play at once
mysynth, myenvsynth,
envbus, // Write the amplitude to this bus
<>server,
<>ndefnamesym // Using Ndefs rather than plain old synths, we need to know what name (symbol) to assign to
;
// CONSTRUCTOR
*new { arg genomeLength=40, server=Server.default, involfreq, newAge=0, ndefname="changeme";
^super.new.init(genomeLength, server, involfreq, newAge, ndefname)
}
init { arg genomeLength, server, involfreq, newAge, ndefname;
// simple initiation stuff
age = newAge;
fitness = 1.0;
pcrossover = 0.5;
pdelete = 0.5;
pmutate = 0.5;
pduplicate = 0.5;
this.server = server;
this.ndefnamesym = ndefname.asSymbol;
if(involfreq==nil, {volfreq = 0.3.linrand + 0.01;},
{volfreq = involfreq;}); // Cycle can be every 5 secs or every 2 mins...
// Initialise the Ndef so it's ready to play my genome
Ndef(this.ndefnamesym).play;
// Initialise the genome
if(genomeLength==nil, {genomeLength=defaultGenomeLength});
genome = {1.0.rand}.dup(genomeLength);
// Now the control bus on the server
envbus = Bus.control(server, 1);
}
mate { arg partner, ndefname="changeme"; // "partner" should be another gaodInd
var child, randloc;
child = GaodInd.new(ndefname:ndefname);
// The following four genetic changes may or may not occur, independently of each other:
// Cross two genomes
if(pcrossover.coin,
{
randloc = min(this.genome.size, partner.genome.size).rand;
if(0.5.coin,
{child.genome = this.genome.copyRange(0, randloc)
++ partner.genome.copyRange(randloc, partner.genome.size-1)},
{child.genome = partner.genome.copyRange(0, randloc)
++ this.genome.copyRange(randloc, this.genome.size-1)}
);
});
// Deletion
if(pdelete.coin,
{
randloc = child.genome.size.rand;
child.genome.removeAt(randloc);
});
// Duplication
if(pduplicate.coin,
{
randloc = child.genome.size.rand;
child.genome = child.genome.insert(randloc, child.genome[randloc]);
});
// Mutation
if(pmutate.coin,
{
randloc = child.genome.size.rand;
child.genome[randloc] = 1.0.rand;
});
^child;
} // End of mate method phenome {
if(phenome == nil, {phenome = GenomeToPhenome.process(this.genome);})
^phenome;
}
play {
if(myenvsynth.isNil,
{
myenvsynth = {Out.kr(envbus.index, LFPar.kr(volfreq, 2, 0.5, 0.5))}.play;
Ndef(ndefnamesym, ("{Pan2.ar(" ++ this.phenome
++ " * In.kr(" ++ envbus.index ++ ", 1), 1.0.bilinrand)}").interpret);
Ndef(ndefnamesym).fadeTime=3;
},
{
("Ndef "+ndefnamesym+" already playing").postln;
});
}
stop {
myenvsynth.free;
myenvsynth == nil;
Ndef(ndefnamesym, {[0,0]});
}
kill { // Kill frees the synths gently - should perhaps have named it something more gentle...
var fadeTime = 3;
Ndef(ndefnamesym).fadeTime = fadeTime;
Ndef(ndefnamesym, {[0,0]});
myenvsynth.free; // Freezes the volume at current level
}
notPlaying {
^myenvsynth.isNil;
}
modifyFitness { |mul|
var modify;
envbus.get({|curamp|
// If curamp == 0, modify = 1
// If curamp == 1, modify = mul
modify = mul ** curamp;
fitness = fitness * modify;
});
}
} // END OF GaodInd class
//-------------------------------------------------------------------------------------------//
GaodIsland {
var <inds, // An array of all the individuals
<>server,
<>cullProportion,
<>delay, // how long to wait between starting each synth
<>yearLength, // how long to wait between kill-and-mate cycles
playTask,
<>maxAge
;
// CONSTRUCTOR
*new { arg numInds=20, genomeLength=20, server=Server.default, cullProportion=0.3, delay=10, yearLength = 30, maxAge=8;
^super.new.init(numInds,genomeLength, server, cullProportion, delay, yearLength, maxAge);
}
init { arg numInds, genomeLength, server, cullProportion, delay, yearLength, maxAge;
this.server = server;
this.cullProportion = cullProportion;
this.delay = delay;
this.yearLength = yearLength;
this.maxAge = maxAge;
inds = Array.newClear(numInds);
inds.do{|ind, index|
inds[index] = GaodInd.new(genomeLength, server, ndefname:("ind"++index));
inds[index].mul = 1.0 / numInds;
};
//("New GaodIsland inited, with " ++ inds.size ++ " individuals").postln;
playTask = Task.new({
inds.do{|ind| if((ind.isNil==false) && ind.notPlaying, {ind.play; delay.wait;})};
inf.do{
yearLength.wait;
this.advanceOneYear;
};
});
} // End of init
play {
playTask.start;
}
stop {
playTask.stop;
inds.do{|ind| ind.stop; };
}
stopGently {
playTask.stop;
inds.do{|ind| ind.kill; };
}
// Multiply the fitnesses of the individuals that are currently sounding.
// Multiply by (e.g.) 0.5 if they're good, (e.g.) 2.0 if they're bad
modifyFitnesses{ |mul|
inds.do({|ind| if(ind!=nil,{
ind.modifyFitness(mul);
})});
}
// This method should carry out all the stuff required for a "year"
// Including ageing the instances, killing off weaklings, mating, etc
advanceOneYear {
var meanfitness=0, cullCands, numToCull;
// This first call to the mate method should normally be useless!
// Its purpose is to catch the rare cases where one of the inds is nil - it will fill in the gap nicely
this.mate;
// Happy birthday!
inds.do{|ind| ind.age = ind.age + 1; };
// Iterate over inds to find mean fitness
inds.do{|ind| meanfitness = meanfitness + ind.fitness; };
meanfitness = meanfitness / inds.size;
//("Mean fitness is "++meanfitness).postln;
// Iterate over inds to find cull candidates (may find more than we want to kill)
cullCands = Array.new(inds.size);
inds.do{|ind, i| if(ind.fitness>=meanfitness, {
//(""++i++" is cullable").postln;
cullCands.add(i)}); };
// Cull the correct number of candidates, plus any that are too old, then mate to fill the slots
numToCull = min(cullCands.size-1 , (inds.size * cullProportion).floor);
cullCands = cullCands.scramble;
Task({
for(0, numToCull, {|cc|
("Killing ind # "++cullCands[cc] ++" (using cullCands["++cc++"])").postln;
inds[cullCands[cc]].kill;
0.5.wait; // A little bit of delay for amortising...
inds[cullCands[cc]] = nil;});
inds.do{|ind, index|
if(ind!=nil, {if(ind.age>maxAge, {
("Killing #"++index++" because of old age of "++ind.age).postln;
ind.kill;
0.5.wait; // A little bit of delay for amortising...
inds[index] = nil;
})});
};
this.mate;
}).play;
}
mate {
var parents = Array.new(inds.size);
// Gather the parents into the "mating pool"
inds.do{|ind, i| if(ind!=nil, {parents = parents.add(ind)})};
// Mate the non-empty slots to create the right # of children
// This must be wrapped in a Task so we can "amortise" by
// using delaying tactics to avoid too many Ndef redefinitions overlapping
Task({
inds.do{|ind, i| if(ind==nil, {
parents = parents.scramble;
("Refilling ind # "++i).postln;
inds[i] = parents[0].mate(parents[1], ndefname:("ind"++i));
inds[i].play;
1.0.wait;
})};
}).play;
}
} // END OF GaodIsland class
//-------------------------------------------------------------------------------------------//
// This (static) class does the conversion from a string of numbers to a graph of UGens
GenomeToPhenome {
classvar iter, <agens, <kgens, mapaudio, mapfreq, mapfreqk;
// Class initialisation - define the UGens to use
*initClass {
// First an array of audio-rate UGens, and an array of control-rate UGens,
// each of which describes how to plug things into them.
// All UGens should be MONO, and CPU-gentle (since dozens and dozens will be used).
// Having done some benchmarking it looks like the following are too CPU-heavy:
// DO NOT USE Pulse, Saw, Blip, FSinOsc, Gendy2 or Gendy1 :(
// FSinOsc is more efficient than SinOsc but unfortunately blows up and destroys the output,
// Each entry contains two elements - UGen name, and an array of inputs.
// The inputs define what goes in (either a static number, or dynamic [rate, remappingexpression])
// All inputs are to be in range +-1 and will be scaled by the mul and add values
// To scale an input to a good range of audible freqs: "*5000+5020"? or "+1**2*2500"? Can it be done efficiently?
mapfreq = ["e","10**(","+1)*50"]; // ["e","10**(","+1)*50"];
mapfreqk = ["k","10**(","+1)*50"]; // ["e","10**(","+1)*50"];
mapaudio = ["a","",""];
agens = [
["SinOsc", [ mapfreq, 0, ["e","",""], mapaudio ]],
//BLOWUP PROBS :( ["FSinOsc", [ mapfreq, 0, ["e","",""], mapaudio ]],
["LFPulse", [ mapfreq, 0, ["k","", "*0.5+0.5"], ["e","",""], mapaudio ]],
["LFSaw", [ mapfreq, 0, ["e","",""], mapaudio ]],
["MidEQ", [ mapaudio, mapfreqk, ["k","",""], ["e","",""], mapaudio ]],
["BPF", [ mapaudio, mapfreqk, ["k","",""], ["e","",""], mapaudio ]],
["RLPF", [ mapaudio, mapfreqk, ["k","",""], ["e","",""], mapaudio ]],
["LPF", [ mapaudio, mapfreqk, ["e","",""], mapaudio ]],
["RHPF", [ mapaudio, mapfreqk, ["k","",""], ["e","",""], mapaudio ]],
["HPF", [ mapaudio, mapfreqk, ["e","",""], mapaudio ]],
["WhiteNoise", [ ["e","",""], mapaudio ]],
["PinkNoise", [ ["e","",""], mapaudio ]],
//CPU-LOAD PROBS :( ["Gendy1", [ 1, 1, 1, 1, ["e","10**(","+1)*40"],["e","10**(","+1)*60"] ]],
["Dust2", [ ["k","",""], ["e","",""], mapaudio ]],
["CombN", [ mapaudio, 0.5, ["k","","+1.1*0.2"], ["k","","*2.0"], ["e","",""], mapaudio ]],
["AllpassN", [ mapaudio, 0.5, ["k","","+1.1*0.2"], ["k","","*2.0"], ["e","",""], mapaudio ]],
["Spring", [ ["e","",""], ["k","","+1.1*10"], ["k","","+1.1*0.05"] ]],
["CuspN", [ ["k","","+1.2*100"], 1, 1.9, 0, ["e","",""], mapaudio ]],
["Ringz", [ mapaudio, mapfreq, ["k", "", "+1.01"], ["e","",""], mapaudio ]],
["Formant", [ mapfreq, mapfreq, ["k","","+1*250"], ["e","",""], mapaudio ]],
["HenonN", [ ["k","","+1.2*100"], 1.4, 0.3, 0, 0, ["e","",""], mapaudio ]],
["LinCongN", [ ["k","","*1e4+1e4"], ["k","","*0.5+1.4"], ["k","","*0.1+0.1"], ["k","",""] ]],
["XFade2", [ mapaudio, mapaudio, ["k","",""] ]],
];
kgens = [
["LFPar", [ ["k","","+1.01"], 0, ["k","",""], ["k","",""] ]], // V slow range
["LFPar", [ ["k","","*50+50.1"], 0, ["k","",""], ["k","",""] ]], // Middling range
["LFPulse", [ ["k","","*50+50.1"], 0, ["k", "","*0.5+0.5"], ["k","",""], ["k","",""] ]],
["VarSaw", [ ["k","","*50+50.1"], 0, ["k","","*0.5+0.5"], ["k","",""], ["k","",""] ]],
["LFNoise0", [ ["e","",""], ["e","",""], ["k","",""] ]],
["LFNoise1", [ ["e","",""], ["e","",""], ["k","",""] ]],
["LFNoise2", [ ["e","",""], ["e","",""], ["k","",""] ]],
];
}
*createGraph { |genome, mode="a", indent=""|
var ret, mygen, argtype;
if(iter>=genome.size,
{ if(mode=="k", {ret = (2.0.rand - 1.0);}, {ret = "SinOsc.ar(exprand(20, 10000))";}) },// If we've run out of codons, return random
{
if(mode=="k" && (genome[iter]>=0.1),
{
ret = ((genome[iter]-0.1)*2.222222)-1;
iter = iter + 1;
},
{
if(mode=="k",
{
mygen = kgens[(genome[iter]*10*kgens.size).floor]; // Choose appropriate kgen
},
{
mygen = agens[(genome[iter]*agens.size).floor]; // Choose appropriate agen
}); // End of just-a-number-or-a-kgen
iter = iter + 1;
// At this point we're definitely using a UGen, and it's stored in "mygen"
// so let's start to build up the SC code for a graph function
ret = "\n" ++ indent ++ mygen[0] ++ if(mode=="k", {".kr("}, {".ar("});
mygen[1].do({ |thisarg, index|
if(thisarg.isKindOf(Array),
{
argtype = thisarg[0];
if(argtype=="e", { if(0.9.coin, {argtype="k";}, {argtype="a";}); });
ret = ret ++ thisarg[1] ++ this.createGraph(genome, argtype, indent++" ")
++ thisarg[2]; // Add the remapping expression (typically something like "*200+400")
if(index!=(mygen[1].size-1),
{ret = ret ++ ",\n" ++ indent ++ " "});
},
{ // It's not an array - i.e. it's a static value
ret = ret ++ thisarg ++ ", ";
});
}); // End of iteration over our UGen's alleged inputs
ret = ret ++ ").clip2(1)";
}); // End of is-it-k-or-a
}
); // End of have-we-run-out-of-codons
^ret; // Return whatever...
} // End of "createGraph" method
// Basically this call the "createGraph" method until the genome has been used up
*process {|genome|
var strs, graphcount;
iter = 0;
graphcount = 0;
strs = "[";
while({iter < genome.size},{ graphcount=graphcount+1; strs = strs ++ "\n{" ++ (this.createGraph(genome)) ++ "}, " });
strs = strs ++ "].mean";
("Created a phenome using " + graphcount + " graph(s)").postln;
^strs;
} // End of "process" method
} // End of GenomeToPhenome class
References
Links to this Page
- test normal last edited on 10 October 2006 at 2:27 am by P8f36.p.pppool.de
- test publication last edited on 16 September 2005 at 4:57 pm by max2-214.dialin.uni-hamburg.de