(
//generate tiling
~berger = {arg width = 10, height = 10, retTiling = true;
	var alpha, beta, gamma, delta,
	a, b, c, d,
	n, j, k,
	r, l, p, s, t, x,
	y, u, z, q,
	tileSet1, tileSet2, tileSet3, words, tiles, colorMap,
	vColorIndex, hColorIndex, vColorMap, hColorMap, vColorAdjust, hColorAdjust,
	tileIter, lastN, relatedTilings = [], tiling, mdArraySort;

	tileSet1 = [
		alpha = [1, 1, 2, 2],
		beta = [2, 3, 1, 4],
		gamma = [3, 2, 4, 1],
		delta = [4, 4, 3, 3]
	];

	tileSet2 = [
		a = ["p", "q", "r", "z"],
		b = ["l", nil, "p", nil],
		c = ["t", "u", "s", "q"],
		d = ["s", nil, "t", nil]
	];

	tileSet3 = [
		n = ["x", "y", nil, nil],
		j = [nil, "z", "x", "u"],
		k = ["r", nil, "l", "y"],

		r = ["r", nil, "r", nil],
		l = ["l", nil, "l", nil],
		p = ["p", nil, "p", nil],
		s = ["s", nil, "s", nil],
		t = ["t", nil, "t", nil],
		x = ["x", nil, "x", nil],

		y = [nil, "y", nil, "y"],
		u = [nil, "u", nil, "u"],
		z = [nil, "z", nil, "z"],
		q = [nil, "q", nil, "q"]
	];

	words = [
		[[alpha], [a, b, c, d]],
		[[beta], [k], [nil, u, q]],
		[[gamma], [j], [l, p, s, t]],
		[[delta], [n], [a, b, c, d]],
		[[delta], [r, l, p, s, t], [nil, u, z, q]],
		[[beta], [y], [nil, u, q], [p, t]],
		[[gamma], [nil, x], [l, p, s, t], [nil, q]],
		[[delta], [l, p, s, t], [nil, q]],
		[[delta], [j], [l, p, s, t]],
		[[beta], [nil, u, z, q], [p, s, t]],
		[[gamma], [r], [nil, q]],
		[[delta], [y], [nil, u, q], [p, t]],
		[[delta], [k], [nil, u, q]]
	];

	// tileSet
	tiles = words.collect({arg word;
		word.allTuples.collect({
			arg elem; elem.removeEvery([nil]).flop.collect({
				arg color; color.removeEvery([nil])
			})
		});
	}).flatten;

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

	vColorMap = Dictionary.new;
	vColorAdjust = [ 17, 4, 15, 11, 0, 3, 18, 23, 1, 13, 19, 5, 10, 21, 14, 8, 25, 20, 12, 9, 16, 7, 24, 2, 22, 6 ] ++ (26..92);
	i = 0;
	(tiles.slice(nil, 0) ++ tiles.slice(nil, 2)).do({arg color;
		if(vColorMap.at(color) == nil, {vColorMap.put(color, vColorAdjust[i]); i = i + 1});
	});

	hColorMap = Dictionary.new;
	hColorAdjust = [ 3, 8, 19, 11, 0, 13, 15, 7, 12, 16, 14, 6, 4, 17, 1, 9, 2, 18, 10, 5 ] ++ (19..92);
	i = 0;
	(tiles.slice(nil, 1) ++ tiles.slice(nil, 3)).do({arg color;
		if(hColorMap.at(color) == nil, {hColorMap.put(color, hColorAdjust[i]); i = i + 1});
	});

	// T_1
	relatedTilings = relatedTilings.add(
		height.collect({arg row;
			width.collect({arg col;
				[[alpha, beta], [gamma, delta]][row % 2][col % 2];
			});
		});
	);

	// T_2
	relatedTilings = relatedTilings.add(
		height.collect({arg row;
			width.collect({arg col;
				[[a, k, b, p], [j, n, nil, nil], [c, s, d, t], [q, nil, nil, nil]][row % 4][col % 4];
			});
		});
	);

	// T_(n >= 3)
	tileIter = 0;
	//lastN = [1, 1];
	while({(pow(2, tileIter + 1).asInteger - 1) < [width, height].maxItem},
		{
			var size, nShift, xShift, yShift, square;
			size = pow(2, tileIter + 3).asInteger;
			nShift = pow(2, tileIter + 1).asInteger - 1;

			square = size.collect({arg row;
				case
				{row == 0} {[a] ++ nShift.collect({r}) ++ [k] ++ nShift.collect({l}) ++ [b] ++ ((nShift * 2) + 1).collect({p})}
				{row <= nShift} {[z] ++ nShift.collect({nil}) ++ [y] ++ (size - (nShift + 2)).collect({nil})}
				{row == (nShift + 1)} {[j] ++ nShift.collect({x}) ++ [n] ++ (size - (nShift + 2)).collect({nil})}
				{row < (size / 2)} {[u] ++ (size - 1).collect({nil})}
				{row == (size / 2)} {[c] ++ ((size / 2).asInteger - 1).collect({s}) ++ [d] ++ ((size / 2).asInteger - 1).collect({t})}
				{row < size} {[q] ++ (size - 1).collect({nil})};

			});

			//not sure why these shifts dont always work and there are always 86 of the 92 tiles used
			//xShift = ((size / 2) * 2.rand).asInteger;
			//yShift = ((size / 2) * 2.rand).asInteger;
			xShift = 0;
			yShift = 0;
			relatedTilings = relatedTilings.add(
				height.collect({arg row;
					width.collect({arg col;
						//square[(row + yShift - lastN[1])  % size][(col + xShift - lastN[0])  % size]
						square[(row + size - nShift)  % size][(col + size - nShift)  % size]
					});
				});
			);

			//lastN = [lastN[0] + xShift + (nShift + 1), lastN[1] + yShift + (nShift + 1)];
			tileIter = tileIter + 1;
	});

	// map colors
	tiling = height.collect({arg row;
		width.collect({arg col;
			relatedTilings.slice(nil, row).slice(nil, col).removeEvery([nil]).flop.collect({arg color, i;
				if((i % 2) == 0, {vColorMap[color.removeEvery([nil])]}, {hColorMap[color.removeEvery([nil])]})
			})
		});
	});

	if(retTiling, {tiling}, {relatedTilings});
};

//generate musical data
~bergerMusicify = {arg relatedTilings, xOffset, yOffset, width, height, size, minDur, maxDur, seed = 11735;
	var subTiling, mdArraySort, tiles, alphaMap, genTileMap, tileMap, startDur, seqs;

	thisThread.randSeed = seed;

	subTiling = relatedTilings.collect({arg tiling; tiling.slice((yOffset..(yOffset + height)), (xOffset..(xOffset + width - 1)))});

	subTiling = height.collect({arg row;
		width.collect({arg col;
			subTiling.slice(nil, row).slice(nil, col).removeEvery([nil]);
			})
		});

	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 = subTiling.flatten.asSet.asList.sort({ |a, b| a.asString < b.asString });

	alphaMap = Dictionary.with(*[
		[1, 1, 2, 2] -> 0,
		[2, 3, 1, 4] -> 1,
		[3, 2, 4, 1] -> 2,
		[4, 4, 3, 3] -> 3
	]);

	genTileMap = {arg tiles;
		var tileMap, scale, amps, odds, harms, quants, durs, dels, preDirs;

		tileMap = Dictionary.new;
		scale = [1/1, 3/2, 5/4, 7/4, 9/8, 11/8, 13/8];
		scale = scale.sort;

		tiles.do({arg t, i;
			var alpha, dim, durRound, dur, density, time, dirs;
			alpha = alphaMap[t[0]];
			dim = [0, 1, 2, 3, 4].wchoose(pow([4, 3, 2, 1, 0.5], 4).normalizeSum);
			scale = ([1/1, 3/2, 5/4, 7/4, 9/8, 11/8, 13/8, 15/8].scramble[0..((4).rand + 1)]).sort * [1/1, 3/2, 5/4, 7/4][dim];
			durRound = [1/4, 1/3, 1/5].choose;
			dur = (2 + 1.0.rand).round(durRound);
			density = (4 / dur).trunc.clip(1, 3).asInteger.rand + 1;
			time = (6 - (density * dur)).rand.round(durRound);
			dirs = density.collect({arg d;
				var figDur, freqRatio, amp, res;

				freqRatio = scale.pop;
				if(freqRatio > 2, {freqRatio = freqRatio / 2});
				freqRatio = freqRatio * pow(2, alpha);
				amp = [0.4, 0.6, 0.8].choose;
				amp = amp * if(alpha == 0, {0}, {((1 / alpha))}) + [0, 0, 0.001, 0.002][alpha];

				res = [time, freqRatio, amp, time + dur, durRound, alpha];
				time = time + dur;
				res
			});

			dirs = dirs.sort({arg a, b; a[0] <= b[0]});
			dirs = dirs.separate({arg a, b; a[0] != b[0]}).collect({arg group; group.choose});
			tileMap.put(t, dirs);

		});
		tileMap
	};

	tileMap = genTileMap.value(tiles);

	startDur = 0;
	seqs = (subTiling.size / 2).trunc.asInteger.collect({arg g;
		var shift, group, res, maxDur, measureDur;

		//if((g % 8) == 0, {tileMap = genTileMap.value(tiles)});

		//read left to right shifting downwards ever 10 columns
		shift = (g/8).trunc.asInteger;
		group = subTiling[(shift * 1)..((shift * 1) + 3)].collect({arg col; col[(g * 2)..((g * 2) + 1)]}).flatten;
		if((shift % 2) == 1, {group = group.rotate(-2)});

		res = group.collect({arg tile, t;
			var dirs, noteDurs, notePitches, del, figDur;
			dirs = tileMap[tile];
			dirs.collect({arg n; [startDur + n[0], n[1], n[2], n[3], n[4]/*, n[5]*/]});
		});

		maxDur = res.flatten.slice(nil, 3).maxItem;
		measureDur = (maxDur + 2.0.rand).ceil(0.5);

		res.collect({arg mDirs; mDirs.collect({arg nDirs; nDirs.add(measureDur).add(startDur)})});

		startDur = startDur + measureDur;

		res
	}).flop;

	seqs.collect({arg seq, i;
		var dur, timeStamps, freqRatio, amp, quant1, quant2, measureDur1, measureDur2, startDur;
		dur = seq.flatten.slice(nil, 0).flatten.differentiate;
		timeStamps = [0] ++ seq.flatten.slice(nil, 0).flatten;
		freqRatio = [Rest(0)] ++ (seq.flatten.slice(nil, 1).flatten);
		seq.flatten.slice(nil, 1).flatten.maxItem;
		seq.flatten.slice(nil, 1).flatten.minItem;
		amp = [Rest(0)] ++ (seq.flatten.slice(nil, 2).flatten);
		quant1 = ([(seq.flatten.slice(nil, 4).flatten)[0]] ++ (seq.flatten.slice(nil, 4).flatten));
		quant2 = (seq.flatten.slice(nil, 4).flatten);
		measureDur1 = ([(seq.flatten.slice(nil, 5).flatten)[0]] ++ (seq.flatten.slice(nil, 5).flatten));
		measureDur2 = (seq.flatten.slice(nil, 5).flatten);
		startDur =  ([0] ++ (seq.flatten.slice(nil, 6).flatten));
		[dur, freqRatio, amp, timeStamps, startDur, measureDur1, measureDur2, quant1, quant2]
	});
};

//create synth defs
~bergerCreateSynths = {arg b;
	// this creates a different waveform for each sonification of each part
	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(\berger, {arg freq, amp = 0.2, 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.25, 0.1), gate, 1, 0, 1, doneAction: 2));
	}).add;
};

//sonify musical data
~bergerSonify = {arg seqs;
	var finSeqs;
	finSeqs = seqs.collect({arg r, i;
		Pbind(\instrument, \berger,
			//\buf, Pseq((i - ((0..(r.slice(nil, 2).flat.size - 1)) / 10).trunc.asInteger) % 8),
			\buf, i,
			//\freq, Pseq(55 * r[1]),
			\freq, Pseq((55 * r[1]).cpsmidi.round(0.5).midicps),
			\dur, Pseq(r[0]),
			\sustain, Pseq(r[0]),
			\amp, Pseq(r[2] / 4)
		)
	});

	Ppar(finSeqs);
};
)