/* For general info, see ISING-TaDa.rtf */

	// useful functions to have
(
q = q ? ();
q.localDir = Document.current.path.dirname; 
	
		// load one ising file
q.readSpin = { |q, path| 
	var text = FileReader.read(path);
	var splitPoints, frames;
	
	text = text.drop(1);		// drop first line, it is empty
//	text[0].postln;
	splitPoints = List[];	// look for breaks between 'images', marked by "!"
	text.do { |line, i| if (line.first.first == $!) { splitPoints.add(i) } };
	splitPoints.postln;

		// put all frames in a List
	frames = List[]; 
	([-1] ++ splitPoints).doAdjacentPairs({ |splitA, splitB, i| 
		frames.add( 
			text.copyRange(splitA + 1, splitB - 1)
				.collect({ |line|
					line.first.as(Array)
						.collect({ |char| char.asString.asInteger });
					});
		);
	});
	frames;
};
		// make a window to show one frame of data
q.makeWin = {	 
	w = SCWindow("Ising(Test)", Rect(30,240, 520, 520)).front;
	w.view.background_(Color.grey);
	w.drawHook = { 
		var black, white;
		black = Color.black; white = Color.white; 
		q.frontFrame.do { |line, i| 
			line.do { |val, j| 
						// set the Color depending on value
				[black, white][val].set;
				Pen.addArc( (i * 5 + 10)@(j*5 + 10), 1.5, 0, 2pi);
				Pen.fill;
			}
		}
	};
	w.refresh;
};
q.makeWin;
)

// read one file and display it: file 1 is quite random
(
q.currFile = "spins-13.data";
q.currFrames = q.readSpin(q.localDir ++ "/Ising-Data/" ++ q.currFile);
q.frontFrame = q.currFrames[0]; w.refresh;
)

	// read all ising files in at once:
(
q.allSpins = ();
[ 	"spins-1.data",
 	"spins-2.data",
 	"spins-3.data",
 	"spins-4.data",
 	"spins-5.data",
 	"spins-6.data",
 	"spins-7.data"
].do { |name, i| 
	q.allSpins.put(
		name.asSymbol, 
		q.readSpin(q.localDir ++ "/Ising-Data/" ++ name)
	)
};
)

q.allSpins.keys.asArray.sort.postcs;

	// put one in front
q.currFrames = q.allSpins['spins-1.data'];

	// and show some of its frames
q.frontFrame = q.currFrames[0]; w.refresh;
q.frontFrame = q.currFrames[1]; w.refresh;
q.frontFrame = q.currFrames[5]; w.refresh;
q.frontFrame = q.currFrames[14]; w.refresh;

	// step thru all the frames in one file
(
Tdef(\show, { 
	q.currFrames.do { |frame, i| 
		i.postln;
		q.frontFrame = frame; w.refresh;
		1.wait;
	};
	"done.".postln;
}).play(AppClock);
)
Tdef(\show).play;

/****** Sonification sketches: *****/
/* what can we listen for? 

1.	local/detail - structure within a single image: 
		size of 0/1 areas, 
		regularity of distribution, 
		...

2.	intermediate - transition from one frame to the next: 
		how many changed fields? 
		some areas stay the same, others change? 
		
3.	global - evolution thru entire series of frames/images: 
		first fast rate of change, end slow? 

4.	(local) aura: selection of subset by mouse/tablet? 
		// is there a mouse/trackpad setting with absolute coordinates? //

*/

	// preparation for audio
p = ProxySpace.push(s.boot);

//	audification of single lines as 100-point transients...
// 	this has not seemed very useful. 
/*
b = Buffer.alloc(s, 101, 1); 
b.sendCollection(q.currFrames.first.first ++ 0.5); // add silence to the end
b.getn(0, 101, { |a| { a.plot }.defer });

~buf.play(vol: 0.1);
~buf = { |rate=1| PlayBuf.ar(1, b.bufnum, rate) - 0.5 ! 2 };
~buf = { |rate=0.1| PlayBuf.ar(1, b.bufnum, rate) - 0.5 ! 2 };
~buf = { |rate=0.01| PlayBuf.ar(1, b.bufnum, rate) - 0.5 ! 2 };
~buf = { |rate=0.25| PlayBuf.ar(1, b.bufnum, rate) - 0.5 ! 2 };

~buf.send;		// tell it to play again
~buf.scope;

	// send one line of data to buffer
b.sendCollection(q.currFrames.first[20]);

(
Tdef(\lines, { 
	~buf.play;
	q.currFrames.first.do { |line, i|
		i.postln;
		b.sendCollection(line ++ [0.5]);
		0.1.wait;
		~buf.send;
		0.1.wait; 
	}
}).play;
)
*/

// better: load one entire image into a buffer, 
// and play the entire buffer.
	
		// make buffer for 10000 values
c = Buffer.alloc(s, 10001, 1);
c.set(10000, 0.5); 	// st end value

		// load data into buffer
c.loadCollection(q.currFrames.first.flop.flat);

		// check that they are there
c.getn(0, 1000, { |a| { a.plot(bounds: Rect(0,0,1008, 200)) }.defer });

//	audification of entire image (linear) 
~buf.play(vol: 0.1);
		// play entire image from buffer, scanned line by line:
~buf = { |rate=1, bufnum| PlayBuf.ar(1, c.bufnum, rate) ! 2 };
~buf = { |rate=0.25| PlayBuf.ar(1, c.bufnum, rate) ! 2 };

~buf.play(vol: 0.1);
~buf.send;

/* 	in the data, the right border is treated as neighboring to 
	left border (and top to bottom), so because adjacent lines 
	are similar, we get a repeating line-frequency, plus noisiness
	for randomness within that waveform.
*/
		// three different frames :
c.loadCollection(q.currFrames[0].flat);	~buf.send;
c.loadCollection(q.currFrames[1].flat);	~buf.send;
c.loadCollection(q.currFrames[2].flat);	~buf.send;

~buf.play;
	// synthetic data tests:
	// white noise:
c.loadCollection({ 2.rand } ! 10000);	~buf.send;
	// left half 1, right half 0: square wave
c.loadCollection( (1.dup(50) ++ 0.dup(50) ! 100).flat); ~buf.send;

(	// play frames in sequence, with pauses:
Tdef(\frames, { 
	q.currFrames.do { |frame|
		c.loadCollection(frame.flop.flat);	  // horizontal scan
		{ q.frontFrame = frame; w.refresh; }.defer;
		
		1.wait;
		~buf.send;
		1.wait;
		
//		c.loadCollection(frame.flat);	  // vertical scan - try with Spin2
//		1.wait;
//		~buf.send;
//		2.wait;
	}
}).play;
)

q.currFrames = q.allSpins['spins-9.data'];
Tdef(\frames).play;

// 3. global view :

// one big buffer for all states in seq:

d = Buffer.loadCollection(s, q.currFrames.collect(_.flop).flat);

	// play entire 15-frame sequence as one sound 
~buf = { |rate=0.5| PlayBuf.ar(1, d.bufnum, rate) ! 2 }; // ca 7 secs
~buf = { |rate=1| PlayBuf.ar(1, d.bufnum, rate) ! 2 };   // ca 3.5 secs
~buf.play(vol: 0.1);

		// vertical scan	
d.loadCollection(q.currFrames.flat);
~buf.send; 

		// horizontal scan	
d.loadCollection(q.currFrames.collect(_.flop).flat);
~buf.send; 

Tdef(\show).play;

// q.frontFrame = q.currFrames[0]; w.refresh;

	// still entire sequence, but now modulating a frequency:
	// noisy is mixed, high tone is area of 1s, low is area 0s
(
~buf = { |rate=0.5| 
	SinOsc.ar(
		PlayBuf.ar(1, d.bufnum, rate) * 300 + 300
	) 
	* Line.kr(0.2, 0.2, BufDur.ir(d.bufnum) / rate, doneAction:2 ) 
	! 2;
};
)

		// falls to lower pitch with spikey noise 

q.currFrames = q.allSpins['spins-9.data'];
		// vertical scan	
d.loadCollection(q.currFrames.flat);
~buf.send; 
	
		// horizontal scan
d.loadCollection(q.currFrames.collect(_.flop).flat);
~buf.send;
~buf.play;

		// two contiguous zones -> loops at image repeat rate
q.currFrames = q.allSpins['spins-2.data'];
d.loadCollection(q.currFrames.flat);
~buf.send; 

		// same with horizontal scanning -> image repeat reate gone!
d.loadCollection(q.currFrames.collect(_.flop).flat);
~buf.send; 
~buf.play;

Tdef(\show).play;

/***	To Try Next From Here: 

	1. 	scan along a Hilbert plane-filling curve to avoid dominance
		of line-freq repetition (the infinite boundaries that cuase that 
		are a technical particularity of the model).

	2. 	zoom-out by downsampling/averaging -> time-compressed screening of long runs
	
	3. 	listen only to the differences between one frame and the next? 
	
	4. 	include secondary params in sonification (e.g. running sum to hear 0/1 dominance)

	4. 	Are subsets / selection interesting at all? 
	
	5. evolution of smaller areas in time? 
	
***/
//data needs to be a 2D-array.
(
q.hilbert = { |e, data, depth=4| // I want to hilbert you
	var stream, array, size;
	if(data.shape.every(_.isPowerOfTwo).not) { Error("need to supply power of two shape").throw };
	size = data.flat.size;
	array = Array.newClear(size);
	stream = HilbertIndex(depth);
	size.do { |i|
		var pair = stream.next;
		var row;
		if(pair.notNil) {
			row = data.at(pair[0]);
			if(row.notNil) {
				array.put(i, row.at(pair[1]))
			}
		}
	};
	array
}
);

// testing whether points are correctly mapped
(
var z;
z = q.hilbert(Array.fill2D(16, 16, { |i, j| i + j }).postln, 6); // test.
z.postln;
z.differentiate.maxItem.postln; // 1
z.differentiate.minItem.postln; // -1
)

q.frontFrame = q.currFrames[0]; w.refresh;

q.hilbertedFrame = q.hilbert(q.frontFrame, 6);

//  sketch for using selection? 
//	play nearest nxn points in a random grain cloud with 2 pitches.
(
SynthDef(\ping, { |out = 0, freq=400, amp=0.2, sus=0.4|
	OffsetOut.ar(out, 
		SinOsc.ar(freq) 
		* XLine.ar(amp, amp * 0.001, sus, doneAction: 2)
	);
}).store;
)
Synth.grain(\ping, [\freq, 300]);

(
Tdef(\cloud).set(\rep, 0.1, \i, 50, \j, 50, \aura, 5, \frame, 0);
Tdef(\cloud, { |e|
	
	inf.do { 
		var ir = e.aura.rand2; 
		var jr = e.aura.rand2; 
		var val = q.currFrames[e.frame].wrapAt(e.i + ir).wrapAt(e.j + e.jr);
		var vector = Point(ir, jr);
		var dir = vector.theta / pi;
		var dist = vector.rho; 
		var amp = (dist + 1).reciprocal * 0.2;
		Synth.grain(\ping, [
			\freq, [300, 500][val], 
			\azi, dir,
			\amp, amp
			]);
		(e.rep * 3).linrand.wait;
	}
}).play;
)

Tdef(\cloud).set(\i, 30, \aura, 5);
Tdef(\cloud).set(\i, 45, \aura, 5);
Tdef(\cloud).set(\i, 49, \aura, 5, \rep, 0.02);

	// why does it sound softer with panaz? 
(
SynthDef(\ping, { |out = 0, freq=400, amp=0.2, sus=0.4, azi=0|
	OffsetOut.ar(out, 
		PanAz.ar(
			4, 
			SinOsc.ar(freq) 
			* XLine.ar(amp, amp * 0.001, sus, doneAction: 2), 
			azi, 
			1
		)
	);
}).store;
)

// add a 2dslider to window to do aura selection
(
t = SC2DSlider(w, Rect(10,10, 500, 500));
t.knobColor_(Color.red);
t.mouseOverAction = { |sl, x, y| 
	[x,y].postln; 
	Tdef(\cloud).set(\i, x - 10 div: 5, \j, y - 10 div: 5);
};
w.acceptsMouseOver_(true);
)
Tdef(\cloud).play;

// listen for differences from one frame to next?
// will work better with frames that are more similar/closer together.

q.frontFrame = (q.currFrames[0]); w.refresh;

q.frontFrame = (q.currFrames[0] - q.currFrames[1]).abs; w.refresh;
q.makeWin;

	// what is the 1/0 ratio? 
q.currFrames.do { |fr| (fr.sumF(_.sumF) / 10000).postln }; ""; 

	// what is the change ratio between frames? 
q.currFrames.doAdjacentPairs { |fr1, fr2| 
	((fr1.flat - fr2.flat).abs.sumF / 10000).postln; 
}; "";