(
//generate tiling: 14 tiles; 6 colors
~kari = {arg rows, cols, offsetX = 0, offsetY = 0, seed = 2/3;
	var s = seed, q, colorMap;

	offsetY.do({
		q = if(s < 1, {2}, {2/3});
		s = s * q;
	});

	//non-reduced colorMap
	colorMap = Dictionary.with(*[
		[ 1, 1 ] -> 0,
		[ 1, 3 ] -> 1,
		[ 0, 1 ] -> 2,
		[ -1, 3 ] -> 3,
		[ 2, 3 ] -> 4,
		[ 0, -1 ] -> 5,
		[ 2, 1 ] -> 6,
		[ -1, 1 ] -> 7
	]);


	//reduced colorMap - This likely introduces an error
	colorMap = Dictionary.with(*[
		//vColors
		[ 1, 1 ] -> 0,
		[ 0, 1 ] -> 1,
		[ 2, 1 ] -> 2,
		//hColors (includes [ 0, 1 ] from above)
		[ 0, -1 ] -> 0,
		[ -1, 3 ] -> 1,
		[ 1, 3 ] -> 2,
		[ 2, 3 ] -> 3,
		[ -1, 1 ] -> 4
	]);


	//reduced colorMap
	colorMap = Dictionary.with(*[
		//hColors
		[ 0, 1 ] -> 0,
		[ 0, -1 ] -> 1,
		[ -1, 3 ] -> 2,
		[ -1, 1 ] -> 3,
		[ 1, 3 ] -> 4,
		[ 2, 3 ] -> 5,
		//vColors (includes [ 0, 1 ] from above)
		[ 1, 1 ] -> 1,
		[ 2, 1 ] -> 2
	]);



	rows.collect({
		var r;
		q = if(s < 1, {2}, {2/3});
		r = cols.collect({arg c;
			var k, t;
			k = c + offsetX;
			t = [
				(q * ((k - 1) * s).floor) - (q * ((k - 1) * s)).floor,
				(k * s).floor - ((k - 1) * s).floor,
				(q * (k * s).floor) - (q * k * s).floor,
				(k * q * s).floor - ((k - 1) * q * s).floor
			].asFraction;
			if((q == (2/3)) && (t[0] == [0, 1]), {t[0] = t[0] * -1});
			if((q == (2/3)) && (t[2] == [0, 1]), {t[2] = t[2] * -1});
			t = t.collect({arg e; colorMap[e]});
		});
		s = s * q;
		r
	});
};

//generate tiling: 13 tiles; 5 colors
~culik = {arg rows, cols, offsetX = 0, offsetY = 0, seed = 1/2;
	var s = seed, q, colorMap, tiling;

	offsetY.do({
		q = if(s < (2/3), {3}, {1/2});
		s = s * q;
	});

	colorMap = Dictionary.with(*[[ 1, 1 ] -> 0, [ 0, 1 ] -> 1, [ 0, -1 ] -> 2, [ 1, 2 ] -> 3, [ 2, 1 ] -> 4]);
	tiling = rows.collect({
		var r;
		q = if(s < (2/3), {3}, {1/2});
		r = cols.collect({arg c;
			var k, t;
			k = c + offsetX;
			t = [
				(q * ((k - 1) * s).floor) - (q * ((k - 1) * s)).floor,
				(k * s).floor - ((k - 1) * s).floor,
				(q * (k * s).floor) - (q * k * s).floor,
				(k * q * s).floor - ((k - 1) * q * s).floor
			].asFraction;

			//tweak colors
			if(q == (1/2), {t = t.collect({arg e, i; if(e == [0, 1] && ((i < 3) || ((s * q) > (2/3))), {e * -1}, {e})})});
			if((t[0] == [0, 1]) && (t[2] == [0, 1]), {t = t.collect({arg e, i; if((e == [0, 1]) && (i < 3), {e * -1}, {e})})});

			//reduce colors
			t = t.collect({arg e; if(e[0] / e[1] != 0, {abs(e)}, {e})});

			//map colors
			t = t.collect({arg e; colorMap[e]});
			t;
		});
		s = s * q;
		r
	});
};

//choose between kari and culik
~kari_culik = {arg rows, cols, offsetX = 0, offsetY = 0, isKari = true;
	if(isKari, {~kari.value(rows, cols, offsetX, offsetY)}, {~culik.value(rows, cols, offsetX, offsetY)});
};


//synths to sonify
~kariCreateSynths = {
	SynthDef(\kariEnsemble, {arg freq = 110, amp = 1, gate = 1, sustain = 1, dur = 1;
		Out.ar([0, 1], SinOsc.ar(freq, 0, 0.1) *
			pow(EnvGen.kr(Env.sine(sustain, amp / 5), gate, 1, 0, 1, doneAction: 2), 0.5) * 0.25);
	}).add;

	SynthDef(\kariBass, {arg freq = 110, amp = 1, gate = 1, sustain = 1, dur = 1;
		Out.ar([0, 1], SinOsc.ar(freq, 0, 0.1) *
			EnvGen.kr(Env.adsr(1, dur - 1.3, 0.25, 0.3, 1), gate, doneAction: 2) * 1);
	}).add;

	SynthDef(\kariNoise, {arg snd = 0, amp = 1, gate = 1, sustain = 1, dur = 1;
		Out.ar([0, 1], Select.ar(snd, [WhiteNoise.ar(0.5), DC.ar(0), BrownNoise.ar(0.7)]) *
			EnvGen.kr(Env.asr(dur, amp / 5, 3), gate, doneAction: 2) * 0.3);
	}).add;
};

//generate music
~kariMusicify = {arg tiling, xOffset, yOffset, width, height, seed = 11735;
	var subTiling, dirs;

	thisThread.randSeed = seed;

	//get a portion of the tiling passed as an argument
	subTiling = tiling.slice((yOffset..(yOffset + height - 1)), (xOffset..(xOffset + width - 1)));

	//turn rows of tiles into two rows of colors each
	//(bottom color is ignored as it is the same as the the top color of the next row)
	dirs = 20.collect({arg sec; subTiling.flop[(sec * 16)..((sec + 1) * 16 - 1)].collect({arg row, r;
		var dur, bass, noise;
		dur = [1, 2, 3].wchoose([row[sec][1] + 1, 2 * (row[sec][3] + 1), 3 * (row[sec][2] + 1)].normalizeSum);
		dur = dur + (3.rand * 0.5);
		bass = row[sec + 1][1];
		noise = row[sec + 2][1];
		[[noise, dur], [bass, dur]] ++ row[sec..(sec + 4)].rotate(sec).collect({arg tile, t; [tile[0], dur]});
	})}).flatten(1);

	dirs.flop.collect({arg row;
		row.separate({arg a, b; a[0] != b[0]}).collect({arg group;
		[group[0][0], group.collect({arg elem ; elem[1]}).sum, group.collect({arg elem ; elem[1]})]})})
};

//generate patterns
~kariSonify = {arg dirs;
	Ppar(
		[
			Pbind(\instrument, \kariNoise,
				\dur,  Pseq(dirs[0].slice(nil, 1) * 1.2),
				\snd, Pseq(dirs[0].slice(nil, 0))),
			Pbind(\instrument, \kariBass,
				\dur,  Pseq(dirs[1].slice(nil, 1) * 1.2),
				\freq, Pseq((dirs[1].slice(nil, 0).collect({arg elem; [Rest(0), 36.midicps, 43.midicps][elem]})))),
		] ++

		dirs.drop(2).collect({arg row;
			Pbind(\instrument, \kariEnsemble,
				\dur, Pseq(row.slice(nil, 1) * 1.2),
				//\sustain, Pseq(row.slice(nil, 1) * 1.2),
				\freq, Pseq(row.slice(nil, 0).collect({arg val; if(val == 5, {Rest(0)}, {(60 + (val.trunc * 7)).midicps})})),
				\amp, 1)})
	);
};
)