/** 
Sonification of the Ising model in 3d

-> reads random arreas of every frame 
-> generates sound according to its mean magnetization (between 0.0 and 1.0)
-> plays each sound grain by mapping its position and using the magnetization for pitch and noisiness

-> gives GUI showing 2 planes of the 3d-model and a controll GUI for the model (starting configuration and coupling) and starts/ends the sonification
**/

// ISING-model in 3d:
(
q.clear;
p.clear;

q = q ? ();
p = p ?? { ProxySpace(s, \p) }; p.push;
s.boot;
)
(	 // instead of q.data -> r.data etc.

q.dimensions = 24;
q.coupling = 0.21;     // equivalent to Tcrit: j = 0.21 (if magn.=0), (range i.e. 0.3 - 0.5)
q.magnetization = 0.0;
//q.timeStep = 0.5;

			// data frame
q.data;

			// functions
q.randData = {|e|
	e.data = Array.fill3D(q.dimensions, q.dimensions, q.dimensions, { #[-1, 1].choose });
	e.data;
};

q.nullData = { q.data = Array.fill3D(q.dimensions, q.dimensions, q.dimensions,  0); q.data; };

q.neighSum = {|e, y, x, z|		// sum of neighbours
	var data = e.data;

	var spinSum = 
		data.wrapAt(z).wrapAt(y).wrapAt(x + 1) + 
		data.wrapAt(z).wrapAt(y).wrapAt(x - 1) +
		data.wrapAt(z).wrapAt(y - 1).wrapAt(x) +
		data.wrapAt(z).wrapAt(y + 1).wrapAt(x) + 
		data.wrapAt(z-1).wrapAt(y).wrapAt(x) +
		data.wrapAt(z+1).wrapAt(y).wrapAt(x);
	spinSum;
};
		// at once, with e.g. dim = 24, this locks computer for 0.5 secs. 
q.nextFrame = { |e|
	q.data.do { |plane, planeIndex|
		q.data[planeIndex].do { |line, lineIndex|
			q.data[planeIndex][lineIndex].do { |spin, colIndex|
				q.data[planeIndex][lineIndex][colIndex] = 
					q.formula(r.neighSum(lineIndex, colIndex, planeIndex), spin);
			}
		}
	}
};

q.nextStep = {|e| e.data = e.nextFrame; e.data };

		// GUI
q.makeWin = {	|r, startPlane=0, endPlane=1|
	
	var size = 5, u;
	u = SCWindow("Ising(Test)", 
		Rect(30,240, 
			size * (q.dimensions + 2) * (endPlane - startPlane + 1), 
			size * q.dimensions + 10)).front;
	u.view.background_(Color.grey);
	
	u.drawHook = { 
		var black, white;
		black = Color.black; white = Color.white; 
		q.data.copyRange(startPlane, endPlane).do { |plane, k|
			plane.do { |line, i| 
				line.do { |val, j| 
							// set the Color depending on value
					[black, white].clipAt(val).set;
					Pen.addArc( ((i * size + 5)+(k*(q.dimensions*size+5) + 5))@((j* size + 10)),
							 1.5, 0, 2pi);
					Pen.fill;
				}
			}
		}
	};
	q.isingWin = u;
	u.refresh;
};

q.updateWin = { defer { try { q.isingWin.refresh } } };

q.formula = {|e, spinSum, spin| // number of neighbours
	var energy, random; 
	energy = -2 * ((q.coupling * spinSum) + q.magnetization) * spin; 
	random = rrand (0.0, 1.0); 
	if (energy >= 0, { 	
		spin * -1
	}, { 					
		if(random <= exp(energy), {
			spin * -1;
		}, {	
			spin
		}); 
	}); 
};

// function that returns an averaged frame
q.fuzzyFrame3 = { |r, x, y, z, size, frame|
	var size1 = size - 1;
	var arr = (x..(x+size1)).collect { |xr|
		(y..(y+size1)).collect { |yr|
			(z..(z+size1)).collect { |zr|
				frame.wrapAt(xr).wrapAt(yr).wrapAt(zr);
			}
		}
	}.flat;
	arr.mean;
};
)
q.randData;
(			// a granular sound for the sonification:

SynthDef("grain", { |out, freq=440, sustain=0.004, pan, ratio=1, noise=0|
	var env, u;
	// envelope with a sine shape (approx. of gabor grain)
	env = EnvGen.ar(Env.sine(sustain, 0.4), doneAction: 2);
	
	u = // sine oscillator w. amplitude compensation for frequency
		SinOsc.ar(freq, 0.5pi, AmpComp.kr(freq, exp: 0.4)) 	
		+ 
		 // and a filtered noise oscillator
		(BPF.ar(ClipNoise.ar, freq, 0.05) * (10 * noise));
	
	Out.ar(out, Pan2.ar(u * env, pan) )// play in two channels
}).store;

// progressing through the data set.
// create a pattern placeholder (Pdefn(\x) )that returns arrays
// of [\freq, \noise, \amp]. \noise being whether or not there is a noise component

Pdefn(\x, 
	Prout({
		10000.do { |i|
			var spinAvg, noise, freq, amp;
			5.do { |i|
					// get average of 3x3x3 data cube. 
				spinAvg = q.fuzzyFrame3(30.rand, 30.rand, 30.rand, 3, q.data);
	
				// play noise only if spin average is close to medium
				noise = (1 - spinAvg.abs - 0.2).max(0);
	
				// scale freqency to spin average
				// spin avg = 
				freq = spinAvg * 400 + 600;	
				// scale amplitude to distance from average (high correlation, high amplitude)
				amp = absdif(spinAvg, 0.0) * 0.1;
				[freq, noise, amp].yield;
			}		
		}
	})
);

// the playing pattern

Pdef(\soni1, 
	Pseq([Pbind(
		\instrument, \grain, // using the grain instrument defined above
		[\freq, \noise, \amp], Pdefn(\x), // assigning the three arguments to the event keys
		\dur, 0.01,
		//\legato, 2
		\sustain, 0.02
	),
	Event.silent(0.5)],inf); // play short silence after a whole round.
);
l=s/d
s=l*d
0.01*2
		// the gradually evolving model:
q.dimensions = 24; q.updateWin; "";

Tdef(\gradualModel, { 
	"model started.".postln;
	q.frameTime = 0.5;
	q.waitTime = q.frameTime / (q.dimensions.squared);
	
	200.do { |i| 
		q.data.do { |plane, planeIndex|
			plane.do { |line, lineIndex|
				line.do { |spin, colIndex|
					q.data[planeIndex][lineIndex][colIndex] = 
						q.formula(q.neighSum(lineIndex, colIndex, planeIndex), spin);
				};
			q.waitTime.wait;
			};
			
		};
		"frame done.".postln;
		if (i % 5 == 0) { q.updateWin };
	};
	"model stopped.".postln;
});
)
//////////////////////////////////////////////////////////////////////////////////////////

// MAKING THE CONTROL GUI:
(
	var v, slid1, b1, b2, b3, b4, b5, b6;

	q.makeWin;

	v=SCWindow("Ising model (3D) and sonification", Rect(100,500,350,180)); 
	v.view.decorator = FlowLayout(v.view.bounds);

	slid1 = EZSlider(v, 300@30, "Coupling", 
		ControlSpec(0.1, 0.9, \lin, 0.01), 
		{|ez| q.coupling=ez.value; q.coupling=ez.value; }
	);

	v.view.decorator.nextLine;

// start and stop model in 3d:
	b1 = SCButton(v,Rect(10,10,100,50));
	b1.states = [
				["model-3D: run",Color.black, Color.new255(212, 212, 212)],
				["model-3D: stop",Color.black, Color.new255(124, 252, 0)]
			];
	b1.action = { arg butt;
		if(butt.value==1, { 
			Tdef(\gradualModel).play;
			//w.front;
			}, {
			Tdef(\gradualModel).stop;
		})	
		};

// start and stop sonification in 3d:
	b2 = SCButton(v,Rect(10,10,100,50));
	b2.states = [
				["sonification: run",Color.black, Color.new255(212, 212, 212)],
				["sonification: stop",Color.black, Color.new255(124, 252, 0)]
			];
	b2.action = { arg butt;
		if(butt.value==1, { 
			Pdef(\soni1).play;
			//w.front;
			}, {
			Pdef(\soni1).stop;
		})	
		};

	v.view.decorator.nextLine;
	b3 = SCButton(v,Rect(120,10,80,30));
	b3.states = [
		["all down", Color.black, Color.red]
	];
	b3.action = { 
		q.data = Array.fill3D(q.dimensions, q.dimensions, q.dimensions,  -1);
		q.isingWin.refresh;
	};

	b4 = SCButton(v,Rect(120,10,80,30));
	b4.states = [
		["randomize", Color.black, Color.red]
	];
	b4.action = { 
		q.randData;
		q.isingWin.refresh;
	};

	b5 = SCButton(v,Rect(120,10,80,30));
	b5.states = [
		["all up", Color.black, Color.red]
	];
	b5.action = { 
		q.data = Array.fill3D(q.dimensions, q.dimensions, q.dimensions,  1);
		q.isingWin.refresh;
	};

v.front;
)

q.data = Array.fill3D(q.dimensions, q.dimensions, q.dimensions,  1);