
(
// generate tiling
~ammann = {arg finalW, finalH, seed = 0;
	var tiles, topLeft, botLeft, topRight, botRight,
	src, dest, temp, w, h, dx, dy, sy, sx, lastRow, dBase, lastCol, cell, count;

	tiles = [
		[0, 1, 1, 0], [3, 2, 2, 3], [2, 4, 4, 2], [5, 3, 3, 5],
		[2, 4, 2, 3], [5, 3, 2, 3], [3, 2, 4, 2], [3, 2, 3, 5],
		[4, 0, 3, 1], [2, 0, 5, 1], [4, 0, 2, 0], [3, 1, 5, 1],
		[1, 5, 0, 2], [1, 3, 0, 4], [1, 5, 1, 3], [0, 2, 0, 4]
	];

	topLeft = [2,9,11,8,11,8,9,9,1,6,7,2,10,8,10,9];
	botLeft = [-1,0,0,0,0,0,0,0,13,15,12,15,-1,-1,-1,-1];
	topRight = [-1,3,3,1,3,7,3,5,-1,-1,-1,-1,4,1,2,5];
	botRight = [-1,12,14,13,12,12,14,13,-1,-1,-1,-1,-1,-1,-1,-1];

	src = Array.fill(finalW * finalH, {0});
	dest = Array.fill(finalW * finalH, {0});
	dest[0] = 0; //Math.abs(Math.floor(seed)) % 16;
	w = h = 1;
	lastRow = lastCol = false;
	count = 0;

	while({((w < finalW) || (h < finalH))}, {
		temp = dest;
		dest = src;
		src = temp;

		dx = dy = 0;

		block({arg breakOuter;
			h.do({arg sy;

				lastRow = (dy == (finalH - 1));
				dx = 0;

				block({arg breakInner;
					w.do({arg sx;
						dBase = dx + (finalW * dy);
						lastCol = (dx == (finalW - 1));
						cell = src[sx + (finalW * sy)];

						dest[dBase] = topLeft[cell];

						if((topRight[cell] != -1) && lastCol.not, {
							dest[dBase + 1] = topRight[cell];
							dx = dx + 2}, {dx = dx + 1});

						if((botLeft[cell] != -1) && lastRow.not, {dest[dBase + finalW] = botLeft[cell]});

						if((botRight[cell] != -1) && lastRow.not && lastCol.not, {dest[dBase + finalW + 1] = botRight[cell]});

						if(dx == finalW, {breakInner.value});
					});
				});

				dy = dy + if(lastRow || (botLeft[cell] == -1), {1}, {2});
				if(lastRow, {breakOuter.value});
			});
		});

		w = dx;
		h = dy;

	});
	dest.collect({arg tile; tiles[tile]}).clump(w);
};

// this creates a different waveform for each sonification of each part
~ammannCreateSynths = {var b;
	Buffer.freeAll(s);
	b = 6.collect({var buf = Buffer.alloc(s, 512, 1); buf.sine1(1.0 / 5.collect({arg i; pow(i + 1, 5.0.rand + 1)}), true, true, true)});

	SynthDef(\ammann, {arg freq, amp, del = 5, gate = 1, sustain = 1, buf = 0;
		Out.ar([0, 1], Osc.ar(Select.kr(buf, b), freq, 0, amp) * EnvGen.kr(Env.adsr(0.05, sustain, 0.5, 0.1), gate, 1, 0, 1, doneAction: 2));
	}).add;
};

~ammannMusicify = {arg tiling, xOffset, yOffset, width, height, size, minDur, maxDur, seed = 11735;
	var subTiling, mdArraySort, tiles, genTileMap, tileMap, seqs;

	thisThread.randSeed = seed;

	//check that this is getting the correct subtiling
	subTiling = tiling.slice((yOffset..(yOffset + height)), (xOffset..(xOffset + width - 1)));

	mdArraySort = { |a, sortFunc|
		if (a.notEmpty) {
			var sorted = a.sort(sortFunc);
			var prev = sorted[0];
			var uniqCount = 1;
			sorted.do { |elem, i|
				if (elem != prev) {
					sorted[uniqCount] = elem;
					uniqCount = uniqCount + 1;
					prev = elem;
				}
			};
			sorted.keep(uniqCount);
		}
	};

	//the tiles should probably just be passed in the constructor
	tiles = mdArraySort.(subTiling.flatten.asSet.asList, { |a, b| a.convertDigits < b.convertDigits });

	genTileMap = {arg tiles;
		var tileMap, amps, odds, harms, quants, durs, dels;
		tileMap = Dictionary.new;
		amps = 4.collect({(1..4).scramble.keep(4)}).flat;
		odds = (1..8).scramble.keep(8).flat * 2 - 1;
		odds = odds.collect({arg harm; if(harm < 4, {harm * 4}, {harm})});
		harms = (odds ++ odds.collect({arg harm; if(harm < 8, {harm * 2}, {harm / 2})})).scramble.keep(16);
		quants = (6.collect({0.25}) ++ 6.collect({1/3}) ++ 6.collect({1/5})).scramble.keep(16);
		durs = ((((maxDur - minDur) / 16) * (0..16)) + minDur).scramble.keep(16);
		dels = ((minDur / 16) * (0..16)).scramble.keep(16);
		durs = dels.collect({arg del; del + (maxDur - del).rand});
		tiles.do({arg t, i;
			var dur, quant, del, sustain, harm, amp;
			dur = durs[i];
			quant = quants[i];
			del = dels[i];
			[dur, del, quant];

			sustain = (3.0.rand + 1); // not used
			harm = harms[i];
			amp = if(harm == (15 / 2), {0.0}, {if(odds.includes(harm), {0.06}, {0.04})});
			tileMap.put(t,[dur, del, sustain, harm, amp, quant]);
		});
		tileMap
	};

	tileMap = genTileMap.value(tiles);

	seqs = subTiling.flop.collect({arg colTmp, c;
		var shift, col, scale, maxDur, figDur;
		//read left to right shifting downwards ever 10 columns
		shift = (c/8).trunc.asInteger;
		col = colTmp[shift..(shift + size)];
		//scales anything time related with the tiles
		//scale = [0.25, 0.5, 1, 2, 3, 4].wchoose([50, 50, 5, 1, 1, 1].normalizeSum);
		scale = [0.125, 0.25, 0.5, 1, 2].wchoose([25, 50, 50, 5, 1].normalizeSum);
		if((c % 32) == 0, {tileMap = genTileMap.value(tiles)});
		//get the maximum duration of the column
		maxDur = col.collect({arg tile; tileMap[tile]}).slice(nil, 0).maxItem * scale;
		//set the total lenghth of the column to be some multiple of 0.5
		figDur = ((maxDur + 1.0.rand) * [1, 1.25, 1.5].wchoose([5, 1, 1].normalizeSum)).trunc(0.5) + 0.5;
		//set min length
		if(figDur < 1, {figDur = 1});
		//for long figures / measures truncate to the second
		if(figDur > 8, {figDur = figDur.trunc});

		col.collect({arg tile, t;
			var dirs, del;
			dirs = tileMap[tile];
			//quanization of the notes is done here
			//so that the scaling does not introduce anything less than a 16th note
			del = (dirs[1] * scale).round(dirs.last);
			[[del, figDur - del], [Rest(1), dirs[2]], [Rest(1), dirs[3]], [0, dirs[4]], dirs[5]]
		});
	}).flop;
};

~ammannSonify = {arg seqs;
	var finSeqs;
	finSeqs = seqs.collect({arg r, i;
		Pbind(\instrument, \ammann,
			//\buf, Pseq((i - ((0..(r.slice(nil, 2).flat.size - 1)) / 10).trunc.asInteger) % 8),
			\buf, i,
			\freq, Pseq((55 * r.slice(nil, 2).flatten).cpsmidi.round(0.5).midicps),
			\dur, Pseq(r.slice(nil, 0).flat),

			//this is a bit tricky it makes it so that each note goes to the next
			//(ignoring the old sustain value)
			//but the rests are kept to make it clear where the note falls in each measure
			//for the transcriber

			\sustain, Pseq(
				[r.slice(nil, 0).flat.drop(1).clump(2).size.collect({Rest(1)}),
					r.slice(nil, 0).flat.drop(1).clump(2).collect({arg item; item.sum})].flop.flat
			),

			//\sustain, Pseq(r.slice(nil, 1).flat  ),
			\amp, Pseq(r.slice(nil, 3).flat * 0.5)
		)
	});

	Ppar(finSeqs);
};

)
