Monday, February 18, 2013
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.
Saturday, January 12, 2013
Sunday, January 6, 2013
Almost Anything in Ruby can be a Hash Key
This was a definite huh / aha moment - finding the idiomatic Ruby way to solve a typical problem, in this case, taking an array of objects, and grouping them in a hash so that each element in the hash was all of the objects belonging to a particular user.
You could parse out some unique value from your user object (which was the brain-damaged PHP way I had been thinking of this problem) and use that has a hash key:
#ugly slug = (post.user.name +" "+post.user.id.to_s).to_sym posts_by_user[slug] << postWhen it turns out that you can use objects themselves as hash keys in Ruby, and then use the Hash.keys method to grab those objects back when you need them. So if you had a Post, which belonged to User, and you wanted to get them all and group them by user (assuming that your ORM or whatever won't do it for you in this instance):
posts = Post.all
posts_by_user = Hash.new {|h,k| h[k]=[]}
posts.each do |post|
#if there is no entry for this user, create one with an empty array
posts_by_user[post.user] ||= []
#shovel the post on to the group for that user
posts_by_user[post.user] << post
end
Iterate through them like this -
posts_by_user.keys.each do |user|
#the user object
p user.inspect
#referencing the hash with the user object as key
posts_by_user[user].each do |post|
#each post belonging to that user
p post.inspect
end
end
Friday, January 4, 2013
Thursday, October 11, 2012
Apache Benchmark Tips
Apache Benchmark (ab) is a great tool for doing quick load testing on websites. Here's a patched version that will let yourun apache benchmark against different pages on your site in the same batch (not just the homepage).
Found via Rainbow Chard
Subscribe to:
Posts (Atom)