/* 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
??
a
/* 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