Monday, February 25, 2013

Fractal Return

Every 12 years or so I get really into fractals and higher dimensional geometry in at least a serious recreational mathematical way. The last 5 years or so I've been doing a lot of music based on distributions of prime numbers (stuff like Continuation and Closure) but at the start of this year, the fractals came back in a big way. I've been playing around with a program called Mandelbulber to explore 3-D renderings of fractal shapes, an d getting back into the math behind generating the Mandelbrot set.

Last time I was this interested in fractals was around 1999 when I was playing with a MIDI-generating program called "Mu Soft Music Generator" (I think) which would create music based on different fractals, L-Systems or periodic oscillators. This was in between the long-term project from 1997-2003, a Q-Basic application that combined twelve-tone and minimalist techniques to play music on the SoundBlaster 16 soundcard - culminating in Writing Software to Play Music to Write Software To. I was trying to wrap my head around Manfred Schroeder's Fractals Chaos and Power Laws. At the time, seeing any kind of rendering of a fractal was a special thing - I feel like it was one of the first things that really caught my interest about the Web (funny how so many pages relating to fractals and fractal art still date from the late 1990's). Something to look forward to in 15 years will be having computers fast enough to explore high-resolution 3-D fractals in realtime.

I just purchased a copy of Experiments in 4 Dimensions which was one of the books that got me started on all of this, back when I was in 4th or 5th grade (circa 1986). I had that book out from the library multiple times, trying to master the code examples given for rendering and rotating a simple wireframe tesseract (on a 4-color, 8mhz, 640k computer). I don't think I ever got one of the programs completed, because having the source code back in 1986 meant that you had a printout of the source code that you could set in your lap while you typed it into your computer (and probably guessed at how to get Apple Basic to run under DOS). I did build some tesseract and hyperpyramid models out of toothpicks.

When I get the book, I'll see if I can translate the code examples from Experiments in 4 Dimensions to Javascript/html5 so people can share it and play with it.

Friday, February 15, 2013

Mandlebrot Sonifications

Here are 2 studies on sonifying iterations of z=z^2+c. I'm using the same instrument from the Carotid Kundalini studies, and mapping the real, imaginary, angle and radius of each iteration to instrument parameters (see the code comments). Each note is actually a chord of iteration values for one point in the set. As the music moves forward in time, parallel rows move along the real axis. What's most interesting to me here is how you can definitely hear cycles, and loops within loops in the pitches, especially in the 2nd study.
//load up all of our servers into an array
~z = Server.all.asArray;

//our instrument - phase multiplication of sine waves, in a cluster of 8 phase / pitch shifted voices
~z.collect({|z|
SynthDef("sine-cluster",{|freq=100, atk=1, rel=1, slope=1, amp=0.005,pan =0,gate=0,m=0,p=0,ph=0,pl=0,pf=1|
 var sin = SinOsc.ar(  [1,1]  * freq *.x (p+[1-p,1,1+p,1+p+p]) ,pl*SinOsc.ar(freq/(pf),ph*(0..3)/3,XLine.ar(freq.sqrt.ceil*pf,pf*slope.abs,pf/(rel*freq.log2.ceil))),(1/(rel+1))+((2-1)/2)*(amp)/(freq+512));
 OffsetOut.ar(0,Mix.ar(Pan2.ar(sin
 ,[-1,1])) * EnvGen.ar(Env.perc(atk,rel, 1,slope),gate, doneAction:2) );}).send(z);
});

//the Mandelbrot function, where z is a complex number and c is a real number
~m = {|z,c|squared(z)+c};

//utility function - returns unique values in an array
~unique = {|a|var t = Array.fill(a.size);
  a.collect({|i,j|(t.includes(i)).if({t.put(j,nil)}, {t.put(j,a[j])})}).reject({|i|i==nil});
  t.reject({|i|i==nil});
};

 

//the pitch set
~p = ~unique.((16..32).reject({|x|x.factors.detect({|n|n>5})!=nil})*.x(2**((-6)..3))).sort;

//some sonification parameters

~globalSustain =2;
~d = 10;
~minIters =5;

//how many iterations to generate the pitches for an orbit
~maxIters = 1024;
~rows =2;
//minibrot on the the real axis
~d = 12;
~minIters =4;
~cx = -1.635;
~ci = -0.0001220703125;
~globalSustain =1.5;
~rows =2;

//on our way out to the western point
~cx = -1.875;
~ci = 0;

~cx = -1.95125;
~ci = -1/(2**30);

~globalSustain =0.5;
~cx = -1.957125;
~ci = 0;
~rows =2;



//5 arm spirals
~globalSustain =2;
~cx = -0.52482350635;
~ci = 0.62534492645;
~rows =3;
~d = 12;
~minIters =6;



(
//start a loop to crawl along the real number axis of the graph
//each cycle zooms in 2x closer to the original point, and crawls 1/2 as quickly
Routine {
(9..9).do({|y|
 var max = (2**(5+y)).asInt;
max.clip(1,2048).do({|x|
 //the player function is defined outside this loop so it can be tweaked in realtime while the loop is playing 
 ~player.(x,y,max);
 })
})
}.play;

/*recursive function to generate a collection of orbit points - bail out if we reach infinity, zero, or a non-number.   Always quit after reaching "safe" number of iterations
this function returns an array of "points" which are the Complex values for a particular iteration
*/
~mr = {|z,c,safe,points|
 ((safe > 0).and(z.real.isNaN.not).and(z.real!=inf).and(z.real !=0)).if({
 ~mr.(squared(z)+c,c,safe-1,points.add(z))
 },points)
};
)
(
~player = {|x,y,max|
 //we sonfiy a line of points on the imaginary axis together, based on the value of ~rows
 ~rows.asInt.do({|ii|
  //slowly crawl along the real axis
  var c= ~cx+(x/(2**(8+y))) - (max/(2**(9+y))) , z = Complex.new(c,~ci+(1/(2**(8+y))*ii)),points;

  //our initial point to iterate on
  z = ~m.(z,c);
 /*
  alternate traversal strategy, move our sonification point in a tightening spiral around the orginal values for real and imaginary.
  var z = Complex(~cx,~ci),points,c=~ci;
  //z is the original center point.  We add the orbit point to this
  z = (z.asPolar+ Polar(1/2**y,0).rotate(((x*pi*2)/128)).scale(1+((1-(x/1024))/(2**(y-ii))))).asComplex;
*/

  //generate an array of orbit points
  points= ~mr.(z,c,~maxIters,[]);
  /* 
  take a slice of the array to sonify it
  the size of this slice can be tweaked by adjusting ~minIters and ~d.
  the lower end of the array tends to be more chaotic and the higher end of the array more constant.
  all of these points sound simultaneously, as a chord
  */
  points.reverse[~minIters.asInt..~d.asInt].collect({|p,i|
   var ss;
   z = p;
   /*
   z is given as a complex number, but we can also treat it as a polar to get its angle (theta) and radius (rho) from the origin (0,0).  it would probably be more interesting to get rho and theta  relative to the original z point.   */
   ((z.real.isNaN)).if({},{
   ss = Synth("sine-cluster",nil,~z.wrapAt(x));
   //frequency of the pitch, based on the distance from the origin
   //fundamental pitch of 66hz multiplied by an overtone in the pitch set ~p
   ss.set(\freq, 66 * ((~p.wrapAt(z.rho*~p.size*~cx))));
   //phase offset of the pitch, also based on radius
   ss.set(\ph,z.rho);
   //add a small pitch bend 
   ss.set(\p,1/512);
   ss.set(\gate,1);
   //this is the amplitude of the 2nd (phase-shifting) oscillator, based on the imaginary value of of z
   ss.set(\pl, z.imag.abs.log2.clip(-1,1));
   //frequency of the 2nd oscillator, based on one of the harmonics in the pitch set ~p.
   ss.set(\pf, 1/(~p.wrapAt(~p.size *z.theta/pi)));
   //length of the note, based on the real value of z
   ss.set(\rel,~globalSustain/((z.real.abs+2).log2).clip(2,32));
   //pannign position
   ss.set(\pan, (z.imag%1) * (z.real%1));
   //note attack value, based on the reciprocal of the imaginary of z (maximum value 1 second)
   ss.set(\atk,1/(1+z.imag.abs));
   //slope of the peak amplitude drop - higher negative value = sharper attack, quieter sustain
   ss.set(\slope,z.real.abs.log * (z.real/z.real.abs));
   //tweak the amplitude a little to offset notes with a longer attack
   ss.set(\amp,2.5  * (2/(1+(z.imag.abs.clip(0,1)))));
   });
  });
 });
 (1/10).wait;
};
)

Visualizing and Sonifying the Iterations of z=z^2+c

This video looks like an interesting point of departure for sonifying the Mandelbrot fractal. I've never been happy with graphical sonifications that map pitch to the y axis of a rendering, and time to the x axis, but mapping these patterns to sound seems like a very promising way to get interesting patterns out of the iterations of the z=^2+c formula. And it would also be something that could render in realtime, instead of rendering millions of points in advance and then putting that data through a sonification function. Here's a blog post on orbit traps. Just started experimenting with Mandelbrot sonifications this week. I'll have some demo's up in another post.