(
// DATA GENEREATOR - this file IS the piece
var frAdd, frDiff, frToFloat, frNearestInList, frCollapse, harmonicDistance,
genMode, hdChoose, wchooseDict, collectRoots,
initModeState, advanceMode,
initTemporalState, genTemporalData,
initPartStates, distributeRoots,
genEnsemblePart, genAccompPart, genBassPart, genAmpCurve, genMusicData, genScoreData, genPatterns;


//~~~~~~~~~~~~FREQUENCY RATIO MATH FUNCTIONS~~~~~~~~~~~~
//for frequency ratios in the form [numerator.factors, denominator.factors]
//we use arrays of factors in order to represent very complex ratios

//add
frAdd = {arg fr0, fr1;
	var num, den;
	num = fr0[0] ++ fr1[0];
	den = fr0[1] ++ fr1[1];
	[[1] ++ num.difference(den).sort, [1] ++ den.difference(num).sort]
};

//difference
frDiff = {arg fr0, fr1;
	var res;
	res = frAdd.value(fr0, fr1.reverse);
	if(frToFloat.value(res) < 1, {res = res.reverse});
	res
};

//convert to float
frToFloat = {arg fr; fr[0].asFloat.product / fr[1].asFloat.product};

//find nearest in list (not sharing the same root or itself)
frNearestInList = {arg frComp, frDict;
	var frNearest, diffNearest, sub;
	frNearest = nil;
	diffNearest = 1000;
	frDict.reject({arg item;
		(item[\root][0] == frComp[1]) ||
		(item[\fr] == frComp[1])
	}).keys.asList.sort({arg a, b; harmonicDistance.value(a) < harmonicDistance.value(b)}).do({arg fr;
		var diff = abs(frToFloat.value(fr) - frToFloat.value(frComp[0]));
		if(diff < diffNearest, {diffNearest = diff; frNearest = fr});
	});
	frNearest
};

//collapse into one octave
frCollapse = {arg fr;
	var res = fr;
	while({frToFloat.value(res) >= 2}, {res = frAdd.value(res, [1, 2])});
	while({frToFloat.value(res) < 1}, {res = frAdd.value(res, [2, 1])});
	res
};

//harmonic distance
harmonicDistance = {arg fr; log2(fr[0].asFloat.product * fr[1].asFloat.product)};


//~~~~~~~~~~~~GENERATE MODE~~~~~~~~~~~~
genMode = {arg forceHS = false;
	var mode, alternateProb;
	alternateProb = [1, 0].wchoose([if(forceHS, {0}, {1}), 4].normalizeSum);
	mode = [
		[1, 1],
		[9, 8],
		[[5, 4], [6, 5]].wchoose([3, if(forceHS, {0}, {1})].normalizeSum),
		[[4, 3], [11, 8]].wchoose([alternateProb, 1].normalizeSum),
		[3, 2],
		[[8, 5], [13, 8]].wchoose([alternateProb, 1].normalizeSum),
		[[15, 8], [7, 4]].wchoose([alternateProb, 1].normalizeSum)
	];
	mode.collect({arg fr; [[1] ++ fr[0].factors, [1] ++ fr[1].factors]});
};


//~~~~~~~~~~~~CHOOSE AND COLLECT FUNCTIONS~~~~~~~~~~~~
hdChoose = {arg mode, exp = 1, weights = [1, 1, 1, 1, 1, 1, 1];
	var probs;
	probs = pow((1 / mode.collect({arg fr;
		harmonicDistance.value(if(fr == [[1], [1]], {[[2], [1]]}, {fr}))})), exp) * weights;
	mode.wchoose(probs.normalizeSum)
};

wchooseDict = {arg dict, exp = 1, limit = 0, isFR = true;
	var keyList, probs;
	keyList = if(isFR, {
		dict.keys.asList.sort({arg a, b; harmonicDistance.value(a) < harmonicDistance.value(b)});
	}, {
		dict.keys.asList.sort({arg a, b; a.convertDigits(2) < b.convertDigits(2)});
	});
	probs = keyList.collect({arg key;
		var count = dict[key][\count];
		if(count < limit, {0}, {count})
	});
	probs = pow(probs, exp);
	keyList.wchoose(probs.normalizeSum)
};

collectRoots = {arg dict; dict.keys.collect({arg fr;
	dict[fr][\root][0]}).asList.sort({arg a, b; harmonicDistance.value(a) < harmonicDistance.value(b)});
};


//~~~~~~~~~~~~GENERATE MODE SEQUENCE~~~~~~~~~~~~
initModeState = {
	var curModeState, frSet;
	curModeState = Dictionary.new;
	frSet = [[1, 1], [9, 8], [6, 5], [11, 8], [3, 2], [13, 8], [7, 4]].collect({arg fr;
		[[1] ++ fr[0].factors, [1] ++ fr[1].factors]});
	frSet.do({arg fr;
		var mode, count;
		mode = genMode.value;
		count = if(fr == [[1], [1]], {10}, {1});
		curModeState.add(fr->
			Dictionary.with(*[\count->count, \mode->frSet, \root->[[[1], [1]], frSet], \mult->fr, \fr->fr]))
	});
	curModeState;
};

advanceMode = {arg lastModeState, lastCadenceState, forceHS = false;
	var curModeState, curRoots, lastRoots, lastCadenceRoot, changeCount, modSpeed;

	curModeState = lastModeState.deepCopy;
	curRoots = collectRoots.value(curModeState);
	lastRoots = collectRoots.value(lastModeState);
	lastCadenceRoot = collectRoots.value(lastCadenceState).asList[0];
	changeCount = 0;
	modSpeed = if(forceHS, {1}, {[1, 2, 3].wchoose([2, 3, 1].normalizeSum)});

	while({
		((curRoots == lastRoots) && (changeCount < modSpeed)) ||
		((changeCount < modSpeed) && (curRoots.size > 1))
	}, {
		var roots, rootSel, mults, multProbs, multSel, new;

		//bump for length of time its been around
		curModeState.keysValuesDo({arg key, val;
			val[\count] = val[\count] + 1;
			if(val[\count] > 100, {val[\count] = 1});
		});

		// max 3 roots that were not the last cadencial root and only in the mode of the last cadence
		roots = curModeState.reject({arg val;
			(val[\fr] == lastCadenceRoot) ||
			lastCadenceState.includesKey(val[\fr]).not || //consider two steps out?
			((curRoots.size >= 3) && curRoots.includes(val[\fr]).not)
		});

		rootSel = wchooseDict.value(roots, 1, 2);
		mults = curModeState[rootSel][\mode];
		multProbs = mults.collect({arg fr;
			if(curModeState.keys.includes(frCollapse.value(frAdd.value(rootSel, fr))), {1}, {2})});
		multSel = hdChoose.value(mults, 0.5, multProbs);
		new = frCollapse.value(frAdd.value(rootSel, multSel));

		curModeState[rootSel][\count] = curModeState[rootSel][\count] + 2; //bump if gets chosen as a root

		if(curModeState.includesKey(new), {
			//bump if it gets chosen again
			curModeState[new][\count] = curModeState[new][\count] + 1;
			if((curModeState[new][\count] >= 20), {
				curModeState[new][\root] = [rootSel, curModeState[rootSel][\mode]]
			});
		}, {
			var old;
			//calculate nearest in list that does not share the same root
			old = frNearestInList.value([new, rootSel], curModeState);
			if(curModeState[old][\count] >= 20, {
				var mode, root;
				root = [rootSel, curModeState[rootSel][\mode]];
				curModeState.add(new ->
					Dictionary.with(*[\count->1,\mode->genMode.value(forceHS), \root->root, \mult->multSel, \fr->new]));
				curModeState.removeAt(old);
				changeCount = changeCount + 1;
			})
		});
		curRoots = collectRoots.value(curModeState);
	});
	curModeState
};


//~~~~~~~~~~~~GENERATE TEMPORAL FRAMEWORK~~~~~~~~~~~~
initTemporalState = {
	Dictionary.with(*[[0, 1], [0, 1], [0, 1]].allTuples.collect({arg tuple;
		tuple->Dictionary.with(*[\count->1])}))
};

genTemporalData = {arg lastTupleState, modeState, cadenceOverride, noParts = 4;
	var cadence, curTupleState, timeToNextEvent, tuple, temporalData;

	cadence = if(collectRoots.value(modeState).size == 1, {cadenceOverride.not}, {false});
	curTupleState = lastTupleState.deepCopy;
	timeToNextEvent = (64 + 50.rand + if(cadence, {50}, {0})).round(16);
	tuple = wchooseDict.value(curTupleState, isFR: false);
	if(cadence, {tuple = [1, 1, 1]});
	curTupleState = curTupleState.keysValuesDo({arg key, val;
		curTupleState[key][\count] = val[\count] + 1});
	curTupleState[tuple][\count] = 0;
	tuple = if(cadence, {[1, 1, 1, 1]}, {[0] ++ tuple});

	temporalData = noParts.collect({arg p;
		var flourishDensity, genDensity, flourish, beforeLen, before, after, buffer;
		flourishDensity = if(tuple[p] == 1, {0.125 + 0.5.rand}, {3});
		if((p == 0) && cadence.not, {flourishDensity = 3});
		genDensity = if(p == 0, {5}, {20});

		flourish = (if(cadence, {16}, {8}) + 32.rand).collect({[0, 1].wchoose([flourishDensity, 1].normalizeSum)});
		buffer = 16.collect({0});
		beforeLen =((timeToNextEvent - flourish.size - buffer.size) /  if(cadence, {1.25}, {1})).asInteger.rand;
		before = beforeLen.collect({arg i; [0, 1].wchoose([genDensity, 0.25].normalizeSum)});
		after = (timeToNextEvent - before.size - flourish.size - buffer.size).collect({[0, 1].wchoose([genDensity, 1].normalizeSum)});
		flourish = before ++ flourish ++ after;
		if(flourish.sum == 0, {flourish[flourish.size.rand] = 1});
		flourish = buffer ++ flourish;
	});

	[temporalData, curTupleState]
};


//~~~~~~~~~~~~GENERATE ENSEMBLE PARTS~~~~~~~~~~~~
initPartStates = {
	var allRatios;
	allRatios = [[1, 1], [9, 8], [5, 4], [6, 5], [4, 3], [11, 8], [3, 2], [8, 5], [13, 8], [15, 8], [7, 4]];
	Dictionary.with(*
		4.collect({arg part;
			part->Dictionary.with(*[\multCounts->
				Dictionary.with(*allRatios.collect({arg fr;
					[[1] ++ fr[0].factors, [1] ++ fr[1].factors]->1})),
				\noteCount->0, \index->part, \lastFreq->0, \lastFreqRatio->[[1], [1]], \lastDur->0
			])
		})
	)
};

//this is how roots are distributed to the parts
distributeRoots = {arg modeState, lastRoots;
	var roots;
	roots = modeState.keys.asList.collect({arg fr; modeState[fr][\root]});
	roots = roots.asBag.contents.asPairs.reverse.clump(2);
	roots = roots = roots.sort({arg a, b;
		if(a[0] != b[0], {a[0] > b[0]}, {frToFloat.value(a[1][0]) > frToFloat.value(b[1][0])})}).collect({arg item; item[1]}).wrapExtend(4);

	roots = [roots[0]] ++ roots[..2].scramble;
	roots = 4.collect({arg part;
		var root, rootMod, rootFreq, mode;
		root = roots[part];
		rootMod = frDiff.value(root[0], lastRoots[part]);
		rootFreq = 40.midicps * pow(2, [1, 0, 1, 2][part]) * frToFloat.value(root[0]);
		[root[0], root[1], rootMod, rootFreq]
	});
	roots = roots.collect({arg root, r;
		var rootRels;
		rootRels = 4.collect({arg p; frDiff.value(root[0], roots[p][0])});
		rootRels.removeAt(r);
		root.add(rootRels)
	});
	roots
};

genEnsemblePart = {arg partState, modeState, temporalData, roots, part, offset;
	var trans, root, mults, rootMod, amp, firstChange, cadence, lastInsRef, ensData;

	trans = pow(2, [1, 0, 1, 2][partState[\index]]);
	# root, mults, rootMod = roots[part];
	amp =  [0, 1, 2, 3].wchoose([0, 2, 2, 2].normalizeSum);
	firstChange = false;
	cadence = if(collectRoots.value(modeState).size == 1, {true}, {false});
	lastInsRef = nil;

	ensData = [];
	temporalData.do({arg val, ts;
		var timeStamp, comp, change;

		partState[\lastDur] = partState[\lastDur] + 1;
		timeStamp = ts + offset;
		change = [val == 1, (val == 1) && firstChange.not].wchoose([1, 2].normalizeSum);
		if(
			(partState[\index] == 0) &&
			(frToFloat.value(partState[\lastFreqRatio]) >= 4.0) &&
			(partState[\lastDur] < 16) && cadence.not,
			{change = false}
		);

		if(change, {
			var mult, multWeights, freq, rootFreq, insRef;

			//this weights notes that are richer and mixes with the DCA algorithm
			multWeights = mults.collect({arg fr;
				var comp = frCollapse.value(frAdd.value(root, fr));
				if(modeState.keys.includes(comp), {3}, {1}) *  pow(partState[\multCounts][fr], 1);
			});

			mult = hdChoose.value(mults, 0.5, multWeights);
			mults.do({arg fr; partState[\multCounts][fr] = partState[\multCounts][fr] + 1});
			partState[\multCounts][mult] = 0;

			freq = 40.midicps * trans * frToFloat.value(frAdd.value(root, mult));

			//flute special case
			if((partState[\index] == 0) && cadence.not, {
				var mode, continue, freqRatio;
				mode = modeState.keys.asList.collect({arg fr;
					[
						frCollapse.value(frAdd.value(modeState[fr][\root][0], modeState[fr][\mult])),
						modeState[fr][\root][0], modeState[fr][\mult]
					]
				});
				mode = mode.sort({arg a, b;
					case
					{frToFloat.value(a[0]) != frToFloat.value(b[0])} {frToFloat.value(a[0]) < frToFloat.value(b[0])}
					{frToFloat.value(a[1]) != frToFloat.value(b[1])} {frToFloat.value(a[1]) < frToFloat.value(b[1])}
					{true} {frToFloat.value(a[2]) < frToFloat.value(b[2])};
				});
				mode = mode ++ mode.collect({arg fr; [frAdd.value(fr[0], [[2], [1]]), fr[1], fr[2]]});
				mode = mode ++ mode.collect({arg fr; [frAdd.value(fr[0], [[4], [1]]), fr[1], fr[2]]});
				continue = true;
				while({continue}, {
					# freqRatio, root, mult = mode[partState[\noteCount] % 15];
					freq = 40.midicps * trans * frToFloat.value(frAdd.value([[1], [1]], freqRatio));
					continue = (freq <= partState[\lastFreq]) && ((partState[\noteCount] % 15) != 0);
					partState[\noteCount] = partState[\noteCount] + 1;
				});
				partState[\lastFreq] = freq;
				partState[\lastFreqRatio] = freqRatio;
				insRef = roots.slice(nil, 0).deepCopy.drop(1).indexOfEqual(root) + 1;
				insRef = if(lastInsRef != insRef, {lastInsRef = insRef; insRef}, {lastInsRef = insRef; nil});
			});
			if((partState[\index] == 0) && cadence, {
				insRef = if(firstChange.not, {1}, {nil});
			});

			rootFreq = 40.midicps * trans * frToFloat.value(root);

			if((partState[\index] == 0) && ((partState[\noteCount] % 15) == 1) && cadence.not, {ensData = ensData.add([0, timeStamp - 8, 0, 0, 0])});
			ensData = ensData.add([freq, timeStamp, amp, mult, insRef]);
			firstChange = true;
			partState[\lastDur] = 0;
			if((partState[\index] == 0) && cadence, {partState[\lastDur] = 32});
		});
	});
	ensData = [[0, ensData[0][1] - 4, 0, 0, 0]] ++ ensData;
	[ensData, partState]
};


//~~~~~~~~~~~~GENERATE ELECTRONIC ACCOMPANIMENT~~~~~~~~~~~~
genAccompPart = {arg modeState, temporalData, offset, trans, part, register;
	var firstChange, accompData;
	firstChange = false;
	accompData = [];
	temporalData.do({arg val, tS;
		var change;
		change = [val == 1, (val == 1) && firstChange.not].wchoose([1, if(part == 0, {5}, {3})].normalizeSum);
		if(change, {
			var sel, freq, amp;
			sel = wchooseDict.value(modeState, 0.1);
			freq = 48.midicps * trans * frToFloat.value(sel);
			amp = [0, 1, 2, 3].wchoose([2, 2, 1, 1].normalizeSum);

			accompData = accompData.add([freq, tS + offset, amp, part]);
			firstChange = true;
		});
	});
	accompData
};


//~~~~~~~~~~~~GENERATE ELECTRONIC BASS~~~~~~~~~~~~
genBassPart = {arg root, ampCurve, hi;
	var freq;
	freq = if(hi,
		{40.midicps * frToFloat.value(frCollapse.value(frAdd.value(root, [[3], [2]])))},
		{40.midicps * frToFloat.value(root)});
	ampCurve.collect({arg sec, iter; [freq, sec[1]]})
};


//~~~~~~~~~~~~GENERATE AMP CURVES~~~~~~~~~~~~
genAmpCurve = {arg temporalData1, temporalData2, offset1, offset2, type;
	var firsts1, firsts2, delay, attack, decay, release, min, max, env;
	firsts1 = temporalData1.collect({arg ptd; ptd.indexOf(1)});
	firsts2 = temporalData2.collect({arg ptd; ptd.indexOf(1)});
	delay = switch(type)
	{0} {0}
	{1} {0}
	{2} {firsts1.minItem};
	attack = switch(type)
	{0} {offset2 - offset1}
	{1} {offset2 - temporalData2[0].size + firsts2.minItem - offset1}
	{2} {firsts1.maxItem - firsts1.minItem};
	decay = switch(type)
	{0} {0}
	{1} {firsts2.maxItem - firsts2.minItem}
	{2} {temporalData1[0].size - firsts1.maxItem};
	release = switch(type)
	{0} {0}
	{1} {temporalData2[0].size - firsts2.maxItem}
	{2} {(offset2 - temporalData2[0].size) - offset1} ;
	min = switch(type)
	{0} {0.15}
	{1} {0}
	{2} {0};
	max = switch(type)
	{0} {0.5}
	{1} {0.65}
	{2} {1};

	env = Env.dadsr(delay, attack, decay, 0.25, release, curve: \cub).range(min, max);
	((delay + attack + decay + release) / 1).asInteger.collect({arg iter; [env.at(iter * 1), offset1 + (iter * 1)]})
};


//~~~~~~~~~~~~GENERATE ALL MUSIC DATA~~~~~~~~~~~~
~genMusicData = {arg seed;
	var minTotalDur, minSection1Dur, dUnit,  curLen, cadence,
	ultimateSubsection, ultimateSection, ultimateCadenceCount,
	minTotalLen, minSection1Len,
	modeState, temporalState, partStates,
	lastCadenceTemporalData, lastCadenceState, lastSectionPoint,
	ensData, accompData, bassData, ampData,
	sectionData, sectionNavDict,
	sectionCount, subsectionCount,
	lastRoots, roots, ampDataTmp;

	thisThread.randSeed = seed;

	# minTotalDur, minSection1Dur, dUnit, curLen, cadence = [23 * 60, 8 * 60, 8.reciprocal, 0, false];
	# ultimateSubsection, ultimateSection, ultimateCadenceCount = [false, false, 0];
	# minTotalLen, minSection1Len = [(minTotalDur / dUnit).round(16), (minSection1Dur / dUnit).round(16)];
	# modeState, temporalState, partStates = [initModeState.value, initTemporalState.value, initPartStates.value];
	# lastCadenceTemporalData, lastCadenceState, lastSectionPoint = [nil, modeState.deepCopy, 0];
	# ensData, accompData, bassData, ampData = [4.collect({[]}), 4.collect({6.collect({[]})}), 2.collect({[]}), 3.collect({[]})];
	# sectionData, sectionNavDict = [Dictionary.new, Dictionary.new];
	# sectionCount, subsectionCount = [1, 1];

	while({(curLen < minTotalLen) || ((curLen >= minTotalLen) && cadence.not) || ultimateSection.not}, {
		var temporalData;
		# temporalData, temporalState = genTemporalData.value(temporalState, modeState, curLen <= minSection1Len);

		collectRoots.value(modeState).collect({arg fr; [fr[0].asFloat.product, fr[1].asFloat.product]}).postln;
		//modeState.keys.postln;
		("-- " ++ sectionCount ++ "." ++ subsectionCount ++ " -----").postln;

		lastRoots = if(curLen == 0, {4.collect({[[1], [1]]})}, {roots.slice(nil, 0)});
		roots = distributeRoots.value(modeState, lastRoots);

		sectionData.add((curLen / 4).asInteger->[roots, lastRoots.collect({arg fr, part;
			[fr, 40.midicps * pow(2, [1, 0, 1, 2][part]) * frToFloat.value(fr)]}), sectionCount, subsectionCount, cadence, ultimateSubsection]);
		sectionNavDict.add([sectionCount, subsectionCount]->[(curLen / 16 + 1).asInteger]);

		4.do({arg part;
			var musicData, partState, accompRoutine;
			# musicData, partState = genEnsemblePart.value(partStates[part], modeState, temporalData[part], roots, part, curLen);
			ensData[part] = ensData[part] ++ musicData;
			partStates[part] = partState;

			//use an independent random number generator for the accompaniment
			accompRoutine = Routine({
				thisThread.randSeed = Date.seed;
				6.do({arg register;
					musicData = genAccompPart.value(modeState, temporalData[part], curLen, pow(2, part + register), part, register);
					accompData[part][register] = accompData[part][register] ++ musicData;
				});
			});
			accompRoutine.value;

		});

		subsectionCount = subsectionCount + 1;

		if(curLen == 0, {
			lastCadenceTemporalData = temporalData;
			lastSectionPoint = curLen;
		});

		curLen = curLen + temporalData[0].size;

		if(curLen > minSection1Len, {
			if(collectRoots.value(modeState).size == 1, {

				ampData[0] = ampData[0] ++ genAmpCurve.value(lastCadenceTemporalData, temporalData, lastSectionPoint, curLen, 0);
				ampDataTmp = genAmpCurve.value(lastCadenceTemporalData, temporalData, lastSectionPoint, curLen, 1);
				ampData[1] = ampData[1] ++ ampDataTmp;
				bassData[0] = bassData[0] ++ genBassPart.value(collectRoots.value(modeState).asList[0], ampDataTmp, true);

				if(sectionCount == 1, {
					ampData[2] = ampData[2] ++ ((curLen - temporalData[0].size) / 1).asInteger.collect({arg iter; [0, iter * 1]});
					bassData[1] = bassData[1] ++ ((curLen - temporalData[0].size) / 1).asInteger.collect({arg iter; [0, iter * 1]})
				}, {
					ampDataTmp = genAmpCurve.value(lastCadenceTemporalData, temporalData, lastSectionPoint, curLen, 2);
					ampData[2] = ampData[2] ++ ampDataTmp;
					bassData[1] = bassData[1] ++ genBassPart.value(collectRoots.value(lastCadenceState).asList[0], ampDataTmp, false);
				});

				sectionData.add(((curLen - temporalData[0].size) / 4).asInteger->
					sectionData[((curLen - temporalData[0].size) / 4).asInteger].put(5, true));
				(subsectionCount - 1).do({arg subsectionIndex;
					sectionNavDict.add([sectionCount, subsectionIndex + 1]->[sectionNavDict[[sectionCount, subsectionIndex + 1]][0], subsectionCount - 1]);
				});
				# sectionCount, subsectionCount = [sectionCount + 1, 1];
				# lastCadenceTemporalData, lastCadenceState, lastSectionPoint = [temporalData, modeState, curLen];
				cadence = true;

				//this should ensure that the final cadence is a HS
				if(curLen >= minTotalLen, {ultimateCadenceCount = ultimateCadenceCount + 1});
				ultimateSection = ultimateCadenceCount > 1;
			}, {
				cadence = false
			});
			modeState = advanceMode.value(modeState, lastCadenceState, curLen >= minTotalLen);
		});
	});

	ampDataTmp = genAmpCurve.value(lastCadenceTemporalData, [[1], [1], [1], [1]], lastSectionPoint, lastSectionPoint + 360, 2);
	ampData[2] = ampData[2] ++ ampDataTmp;
	bassData[1] = bassData[1] ++ genBassPart.value(collectRoots.value(lastCadenceState).asList[0], ampDataTmp, false);

	[ensData, accompData, bassData, ampData, sectionData, sectionNavDict]
};
)

