View this PageEdit this PageUploads to this PageHistory of this PageTop of the SwikiRecent ChangesSearch the SwikiHelp Guide

test normal

Home   How To   Code Pool   Public Library   Theory   Events
/*
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



Link to this Page