(
//generate tiling
~penrose = {arg tDim = 20, pDim = 5, seed = 11735;
	var grids, shifts, vColorMap, hColorMap, genRhombusMidpoint, numLines, rows, cols, tiling;

	thisThread.randSeed = seed;

	grids = pDim.collect({arg d; (pi * complex(0, ((pDim % 2) + 1)) * d / pDim).exp});
	shifts = pDim.collect({1.0.rand}).normalizeSum;

	vColorMap = Dictionary.with(*[
		[ [ 0.0, 0.95 ], [ 0.25, 1.72 ], [ 0.25, 2.31 ] ].asString -> 0,
		[ [ 0.56, 0.77 ], [ 0.81, 1.54 ] ].asString -> 1,
		[ [ -0.25, 0.77 ], [ -0.81, 1.54 ] ].asString -> 2,
		[ [ 0.0, 0.95 ], [ -0.56, 1.72 ] ].asString -> 3,
		[ [ -0.25, 0.77 ], [ -0.25, 1.36 ], [ 0.0, 2.13 ] ].asString -> 4,
		[ [ -0.25, 0.77 ], [ -0.81, 1.54 ], [ -0.56, 2.31 ] ].asString -> 5,
		[ [ 0.0, 0.95 ], [ 0.25, 1.72 ] ].asString -> 6,
		[ [ 0.56, 0.77 ], [ 0.81, 1.54 ], [ 0.25, 2.31 ] ].asString -> 7,
		[ [ 0.0, 0.95 ] ].asString -> 8
	]);

	hColorMap = Dictionary.with(*[
		[ [ 0.9, -0.29 ], [ 1.71, -0.29 ] ].asString -> 0,
		[ [ 0.9, -0.29 ] ].asString -> 1,
		[ [ 0.9, 0.29 ], [ 1.71, 0.29 ], [ 2.27, -0.48 ] ].asString -> 2,
		[ [ 0.65, -0.48 ], [ 1.21, -0.66 ], [ 2.02, -0.66 ] ].asString -> 3,
		[ [ 0.65, -0.48 ], [ 1.21, -1.24 ] ].asString -> 4,
		[ [ 0.65, -0.48 ], [ 1.21, -1.24 ], [ 2.02, -1.24 ] ].asString -> 5,
		[ [ 0.9, -0.29 ], [ 1.71, -0.29 ], [ 2.27, -0.48 ] ].asString -> 6,
		[ [ 0.9, -0.29 ], [ 1.46, -1.06 ] ].asString -> 7,
		[ [ 0.9, 0.29 ], [ 1.71, 0.29 ] ].asString -> 8
	]);

	genRhombusMidpoint = {arg r, s, kr, ks;
		var intersectPoint, index, rhombus;

		intersectPoint = (grids[r] * (ks - shifts[s]) - (grids[s] * (kr - shifts[r]))) * complex(0, 1) / grids[(s-r) % grids.size].imag;
		index = [grids, shifts].flop.collect({arg gsPair; ((intersectPoint / gsPair[0]).real + gsPair[1]).ceil});
		rhombus = [[kr, ks], [kr+1, ks], [kr+1, ks+1], [kr, ks+1]].collect({arg kPair;
			(index.put(r, kPair[0]).put(s, kPair[1]) * grids).sum
		});

		[(rhombus[0].real + rhombus[2].real) / 2, (rhombus[0].imag + rhombus[2].imag) / 2]
	};


	numLines = tDim + 1;
	rows = Array.fill(numLines + 1, {[]});
	cols = Array.fill(numLines + 1, {[]});

	//fill
	2.do({arg r; var krMin, krMax, ksMin, ksMax, min, max, axis, tmpList;
		(0..numLines).do({arg kr;
			((r + 1)..(pDim - 1)).do({arg s;
				((-2 * numLines)..(2 * numLines)).do({arg ks;
					var rhombus, mp;
					mp = genRhombusMidpoint.value(r, s, kr, ks);

					if((r == 0), {cols[kr] = cols[kr].add([mp, s])});
					if((r == 1), {rows[kr] = rows[kr].add([mp, s])});
					if((s == 1) && (ks >= 0) && (ks <= numLines), {
						rows[ks] = rows[ks].add([mp, s])
					});
				});
			});

			//trim
			if(r == 0, {
				krMin = krMax = kr; ksMin = 0; ksMax = numLines;
			}, {
				krMin = 0; krMax = numLines; ksMin = ksMax = kr;
			});
			min = genRhombusMidpoint.value(0, 1, krMin, ksMin);
			max = genRhombusMidpoint.value(0, 1, krMax, ksMax);
			axis = (r - 1).abs;
			tmpList = if(r == 0, {cols[kr]}, {rows[kr]});
			tmpList = tmpList.reject({arg item;
				(item[0][axis] < min[axis]) || (item[0][axis] > max[axis])});
			//sort
			tmpList = tmpList.sort({arg a, b; a[0][axis] < b[0][axis]});
			//group
			tmpList = tmpList.separate({arg a, b; (a[1] != 1) && (b[1] == 1)});
			tmpList.pop;
			//remove corners
			tmpList = tmpList.collect({arg g;
				var shift = g[0][0];
				g[1..].collect({arg item; (item[0] - shift).round(0.01)});
			});
			//reset
			if(r == 0, {cols[kr] = tmpList}, {rows[kr] = tmpList});
		})
	});

	//map colors
	tiling = Array.fill2D(tDim, tDim, {arg r, c;
		[
			vColorMap[cols[c][r].asString],
			hColorMap[rows[r][c].asString],
			vColorMap[cols[c+1][r].asString],
			hColorMap[rows[r+1][c].asString]
		];
	});
};

//generate musical data
~penroseMusicify = {arg tiling, xOffset, yOffset, width, height, size, minDur, maxDur, seed = 11735;
	var subTiling, mdArraySort, tiles, genTileMap, measureLens, sectionLens, seqs, secLengths, subSecLengths;

	thisThread.randSeed = seed;

	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);
		}
	};

	tiles = mdArraySort.(subTiling.flatten.asSet.asList, { |a, b| a.convertDigits < b.convertDigits });

	genTileMap = {
		(tiles.flat.maxItem + 1).collect({arg val;
			var randSel = [0, 1].wchoose([0.2, 0.8]);
			[
				[8.collect({(1..(2 + 3)).choose}).scramble[0..(3.rand + 3)], [10.rand, 10.rand]],
				[0, 1][randSel],
				[
					[1/1, 1/1, 1/1, 17/16, 9/8, 6/5, 6/5, 6/5, 3/2, 3/2, 3/2, 7/4, 7/4].scramble.[0..(5 + 8.rand)] * [1, 2].choose,
					[1/1, 1/1, 1/1, 17/16, 9/8, 6/5, 6/5, 6/5, 11/8, 11/8, 3/2, 3/2, 3/2, 7/4, 7/4].scramble * [1, 2].choose
				]
			]
		});
	};

	sectionLens = 1000.collect({3.rand + 5});
	measureLens = 1000.collect({80.rand + 20}).clumps(sectionLens).collect({arg sec; sec.integrate});
	secLengths = [];
	subSecLengths = 6.collect({[]});

	seqs = 20.collect({arg section;
		var subSeqs, totalDurs, max, tileMap;
		totalDurs = [0, 0, 0, 0, 0, 0];
		tileMap = genTileMap.value;
		tileMap;
		subSeqs = 6.collect({arg r;
			sectionLens[section].collect({arg c;
				var durs, amps, notes, iter, finalDurs, finalNotes, finalAmps, res;

				durs = if(r == subTiling[r + section][c][0],
					{tileMap[subTiling[r + section][c][1]][0][0]}, {[5 + 10.rand, 5 + 10.rand] + tileMap[subTiling[r + section][c][1]][0][1]});

				amps = if(r == subTiling[r + section][c][0],
					{[1] ++ (subTiling[r + section][c][2] + 1).asDigits(2, 4)}, {[1]});

				notes = if(r == subTiling[r + section][c][0],
					{tileMap[subTiling[r + section][c][3]][2][0] * pow(2, r)}, {tileMap[subTiling[r + section][c][3]][2][1] * pow(2, r)});

				iter = 0;
				finalDurs = [];
				finalNotes = [];
				finalAmps = [];
				while({(totalDurs[r] + (durs[iter % durs.size] * amps[iter % amps.size])) < measureLens[section][c]}, {
					if((c == 0) && (iter == 0),
						{
							finalDurs = finalDurs.add(10.rand + 5);
							finalNotes = finalNotes.add(notes[iter % notes.size]);
							finalAmps = finalAmps.add(0);
							totalDurs[r] = totalDurs[r] + finalDurs.last;
						},
						{
							if((iter > 1) , {
								finalDurs = finalDurs.add(durs[iter % durs.size] * amps[iter % amps.size]);
								finalNotes = finalNotes.add(notes[iter % notes.size]);
								finalAmps = finalAmps.add(amps[iter % amps.size]);
								totalDurs[r] = totalDurs[r] + finalDurs.last;
							},
							{
								finalDurs =  finalDurs.add(10.rand + 5);
								finalNotes = finalNotes.add(notes[iter % notes.size]);
								finalAmps = finalAmps.add(1);
								totalDurs[r] = totalDurs[r] + finalDurs.last;
							});

						};
					);
					iter = iter + 1;
				});

				finalAmps[finalAmps.size-1] = 1;
				finalDurs[finalDurs.size-1] = finalDurs.last + (measureLens[section][c] - totalDurs[r]) + 5.rand + 15;
				totalDurs[r] = totalDurs[r] + (measureLens[section][c] - totalDurs[r]);

				subSecLengths[r] = subSecLengths[r].add(finalDurs[0]);
				subSecLengths[r] = subSecLengths[r].add(finalDurs.sum - finalDurs[0]);

				res = [finalDurs, finalNotes, finalAmps].flop.select({arg item; item[0] != 0});

			}).flatten.flop
		});

		max = subSeqs.collect({arg elem; elem[0].sum}).maxItem;
		max = max + (16 - (max % 16));
		secLengths = secLengths ++ [(max / 16).asInteger];
		subSeqs = subSeqs.collect({arg elem, i;
			subSecLengths[i] = subSecLengths[i].add(max - elem[0].sum);
			[elem[0] ++ [max - elem[0].sum], elem[1] ++ [0], elem[2] ++ [0]]});

		subSeqs;
	});

	seqs = 6.collect({arg part;
		[seqs.slice(nil, part).slice(nil, 0).flat,
			seqs.slice(nil, part).slice(nil, 1).flat,
			seqs.slice(nil, part).slice(nil, 2).flat] });
	[seqs, secLengths, subSecLengths]
};

//create synths
~penroseCreateSynths = {arg b;
	Buffer.freeAll(s);
	b = 8.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(\penrose_ins, {arg freq, amp = 0.2, del = 5, gate = 1, sustain = 1, buf = 0;
		Out.ar([0, 1] /*10 + buf*/, Osc.ar(Select.kr(buf, b), freq, 0, amp) * EnvGen.kr(Env.adsr(1, sustain - 0.3, 0.7, 0.5), gate, 1, 0, 1, doneAction: 2));
	}).add;

	SynthDef(\penrose_fades, {arg subSeqLength, gate = 1, ins = 0;
		Out.ar([0, 1], In.ar(10 + ins) * EnvGen.kr(Env.new([0.4, 0.8, 0.8, 0.4, 0], [3, subSeqLength - 6, 3, 0.1], \sine), gate, 1, 0, 1, doneAction: 2));
	}).add;
};


//generate patterns
~penroseSonify = {arg seqs;
	var finSeqs, finFades;
	finSeqs = seqs[0].collect({arg r, i;
		Pbind(\instrument, \penrose_ins,
			\buf, i,
			//\freq, Pseq((24.midicps * r[1]).cpsmidi.round(0.5).midicps),
			\freq, Pseq((24.midicps * r[1])),
			\dur, Pseq(r[0] * 0.125),
			\sustain, Pseq(r[0] * 0.125),
			\amp, Pseq(((r[2] * 0.25) / pow(1.5, ((i % 6) + 1))))
		)
	});

	finFades = seqs[2].collect({ arg r, i;
		Pbind(\instrument, \penrose_fades,
			\dur, Pseq(r * 0.125),
			\subSeqLength, Pseq(r * 0.125),
			\ins, i
		)
	});

	Ppar(finSeqs /*++ finFades*/);
};
)
