(
var genInitSeq, finalizeSeqs, finalizeAccompHigh, finalizeAccompLow;

//~~~~init vars for initial sequence generation
genInitSeq = {arg seed = 20200525;
	var setDur, strings, stringIndex, state, lastStrings, position, dur, openStringCount, landingCount, sectionCount,
	modelInitSeq, res;

	thisThread.randSeed = seed;

	//~~~~helper dur function
	setDur = {arg probs; [2, 3, 4, 5.rand + 3].wchoose(probs.normalizeSum)};

	modelInitSeq = 16.collect({
		[
			//probabily adjustment for altering picking pattern
			2 + 1.0.rand2,
			//probabilities adjustment for position
			7 + 2.0.rand2,
			//probabilities for inserting walk down
			2.5 + 1.5.rand2,
			//penulitmate position
			6.collect({2.rand + 1}),
			//probabilities for adjustment to duration
			3 + 1.0.rand2,
			//probabilities for note durations
			[5 + 2.0.rand2, 5 + 2.0.rand2, 5 + 2.0.rand2, 1],
			//probabilities adjustment for altering state
			2 + 1.0.rand2,
			//number of notes in ultimate section
			12 + 4.rand
		]
	});

	strings = (0..5);
	state = 6.collect({[0, 1].wchoose([2, 1].normalizeSum)}); //fretted or not
	lastStrings = [nil, nil];
	position = 6.collect({10 + 3.rand}); //which frets
	dur = setDur.value(modelInitSeq[0][5]);
	openStringCount = 0;
	landingCount = 0; //for extending section landing on open strings
	sectionCount = 0;

	res = []; //notes before the more static repetitions are put in

	//~~~~run routine and create template sequence
	//~~~~number of sections must be even - generate 16 by default
	({sectionCount < 16}).while({
		var alterPattern, penultimatePos, lastFrettedString, forceUltimateDescent;

		//alter string pattern or not
		penultimatePos = (position.sign.sum == 1);
		if(penultimatePos, {lastFrettedString = position.sign.indexOf(1)});
		forceUltimateDescent = penultimatePos && strings.includes(lastFrettedString).not;
		alterPattern = [true, forceUltimateDescent].wchoose([1, modelInitSeq[sectionCount][0]].normalizeSum);
		if(alterPattern, {
			//var lastFrettedString;
			strings = (0..5).scramble[..(4.rand + 1)];
			//keep selecting until you have the final string
			while({forceUltimateDescent && strings.includes(lastFrettedString).not}, {
				strings = (0..5).scramble[..(4.rand + 1)];
			});
			//rotate if a note gets repeated
			if(lastStrings.last == strings.first, {strings = strings.rotate});
			lastStrings = strings;
		});

		//iterate through the strings
		strings.do({arg string, stringIndex;
			var alterPos;

			//alter fret if fretted and keeping hand in similar position
			alterPos = (position[string] * state[string]) > 0;
			alterPos = alterPos && (state[string] == 1); //isFretted
			alterPos = alterPos && (position[string] > (position.maxItem - 3));
			alterPos = [alterPos, false].wchoose([modelInitSeq[sectionCount][1], 1].normalizeSum);
			if(alterPos, {
				var walkDown, stepLimit;

				//walk down or not
				walkDown = [true, false].wchoose([1, modelInitSeq[sectionCount][2]].normalizeSum);
				if(walkDown, {
					res = res.add([string, state[string] * position[string], dur, position.deepCopy]);
				});

				//make sure a hand position is not too wide
				stepLimit = (position.maxItem - (position[string] - 2)) != 4;
				if(stepLimit.not, {
					position[string] = (position[string] - 1).clip(0, 12);
				});
				if(stepLimit, {
					position[string] = (position[string] - [1, 2].choose).clip(0, 12);
				});
			}, {
				if((position[string] <= modelInitSeq[sectionCount][3][string]) && (state[string] == 0), {position[string] = 0});
			});

			//alter duration or not
			if([true, false].wchoose([modelInitSeq[sectionCount][4], 1].normalizeSum), {dur = setDur.value(modelInitSeq[sectionCount][5])});

			//add
			res = res.add([string, state[string] * position[string], dur, position.deepCopy]);

			//alter state or not favoring off if on (determines if string is open or fretted)
			if(sectionCount.even, {
				var isFretted, probs, alterState;
				isFretted = (state[string] == 1);
				probs = [if(isFretted, {modelInitSeq[sectionCount][6]}, {1}), 1].normalizeSum;
				alterState = [true, false].wchoose(probs);
				if(alterState, {state[string] = (state[string] + 1) % 2});
			});

			//alternate option
			if(sectionCount.odd, {
				var isFretted, alterable;
				isFretted = (state[string] == 1);
				alterable = isFretted || ((state[string] == 0) && (state.sum < 3));
				if(alterable, {
					var probs, alterState;
					probs = [if(isFretted, {1}, {modelInitSeq[sectionCount][6]}), 1].normalizeSum;
					alterState = [true, false].wchoose(probs);
					if(alterState, {state[string] = (state[string] + 1) % 2});
				});
			});

			//reset if everything arrives at the bottom string
			if(position == [0, 0, 0, 0, 0, 0], {
				var noNotes, hasLanded;
				noNotes = modelInitSeq[sectionCount][7];
				hasLanded = (landingCount > noNotes) && (stringIndex == (strings.size - 1));
				if(hasLanded, {
					(landingCount - 1).do({arg index;
						res[res.size - index - 1][2] = (dur * (1 + ((1 - (index / landingCount).clip(0, 1).pow(0.5)) * 8))).asInteger
					});
					position = 6.collect({10 + 3.rand});
					landingCount = 0;
					sectionCount = sectionCount + 1;
				});
				if(hasLanded.not, {landingCount = landingCount + 1})
			});
		})
	});
	res
};


//~~~~insert more static sections by repeating a figure
finalizeSeqs = {arg initSeq;
	var modelReps, extendToBeat, insertTS,
	timeStampSection, timeStampTotal, timeStampSectionStart, lastDur, lastPos,
	sectionSeq, timeSigInsSeq, state, sectionCount, guitarSeq;

	modelReps = 16.collect({
		[
			//where in the descent will the repetitions occur
			4 + 4.rand,
			//length of repetition
			15.rand + 5,
			//number of repetitions
			10.rand + 5,
			//probabilities for keeping a note in the repetition
			5 + 1.0.rand2,
			//max interval of bass part in repetitions
			5 + 3.rand
		]
	});

	extendToBeat = {arg seq, round = 4;
		var timeStampTotal, altEndDur;
		//this makes sure it is some multiple of a beat
		timeStampTotal = seq.slice(nil, 2).sum;
		altEndDur = timeStampTotal.round(round) - timeStampTotal;
		//must remain larger than a 16th notes
		if((seq.last[2] + altEndDur) <= 1, {altEndDur = altEndDur + round});
		seq.last[2] = seq.last[2] + altEndDur;
		[seq, altEndDur];
	};

	insertTS = {arg seq, timeStampSectionStart, type, accompSwitch;
		var timeStampTotal, noMeasures;
		timeStampTotal = seq.slice(nil, 2).sum;
		sectionSeq = sectionSeq.add([timeStampTotal, type, accompSwitch]);
		noMeasures = ((timeStampTotal - timeStampSectionStart) / 16);
		if(noMeasures.frac > 0, {
			timeSigInsSeq = timeSigInsSeq.add(
				// make 3/2 instrad of 1/2
				if((noMeasures.frac / 0.25).asInteger != 2, {
					[timeStampTotal - (4 * (noMeasures.frac / 0.25).asInteger),(noMeasures.frac / 0.25).asInteger]
				}, {
					[timeStampTotal - (4 * 6), 6]
				});
			);
			timeSigInsSeq = timeSigInsSeq.add([timeStampTotal, 4]);
		});
	};

	timeStampSection = 0; //track time in each section
	timeStampTotal = 0; //track overall time
	timeStampSectionStart = 0; //track the time of the start of a section
	lastDur = initSeq[0][2]; //helper for time signature data
	lastPos = initSeq[0].last; //helper for keeping track of landing.

	guitarSeq = []; //this is the final sequence with repetitions inserted
	sectionSeq = [[0, 0, true]]; //sequence of times for each section (used for double bars in score)
	timeSigInsSeq = [[0, 4]]; //sequence for insertion of time signatures and double bars;

	state = 0;
	sectionCount = 0;

	initSeq.do({arg item, index, altEndDur;
		var dur, pos;
		dur = item[2];
		pos = item.last;

		if(state != 1, {
			// basically this just copies the original template over
			var landingBorder, sectionBorder;
			landingBorder = (pos == [0, 0, 0, 0, 0, 0]) && (lastPos != [0, 0, 0, 0, 0, 0]);
			sectionBorder = (pos != [0, 0, 0, 0, 0, 0]) && (lastPos == [0, 0, 0, 0, 0, 0]);
			if(landingBorder || sectionBorder, {
				var seqExtPair;
				seqExtPair = extendToBeat.value(guitarSeq, 8);
				guitarSeq = seqExtPair[0];
				timeStampSection = timeStampSection + seqExtPair[1];
				insertTS.value(guitarSeq, timeStampSectionStart, if(pos == [0, 0, 0, 0, 0, 0], {1}, {-1}), false);
				timeStampSectionStart = guitarSeq.slice(nil, 2).sum;
			});

			if(sectionBorder, {
				state = 0;
				sectionCount = sectionCount + 1;
			});

			guitarSeq = guitarSeq.add(item.add(-1));
			timeStampSection = timeStampSection + dur;

			lastDur = dur;
			lastPos = pos;

			if((state == 0) && (pos.minItem < modelReps[sectionCount][0]), {state = 1});
		});

		if(state == 1, {
			// grabs a figure and repeats it altering it subtly
			var rec, reps, noMeasures;

			guitarSeq = extendToBeat.value(guitarSeq, 8)[0];
			timeStampTotal = guitarSeq.slice(nil, 2).sum;
			insertTS.value(guitarSeq, timeStampSectionStart, 0, true);
			timeStampSectionStart = timeStampTotal;
			rec = guitarSeq[(guitarSeq.size - modelReps[sectionCount][1])..guitarSeq.size].deepCopy;
			reps = modelReps[sectionCount][2];
			reps.do({arg index;
				rec.do({arg item, rIndex;
					var add, dur;
					add = if(index == 0, {3}, {0});
					dur = (item[2] + 2.rand2 + add);
					if(dur < 2, {dur = [0, 2].wchoose([1, 4].normalizeSum)});
					rec[rIndex] = [item[0], item[1], dur];
					if([true, false].wchoose([modelReps[sectionCount][3], 1].normalizeSum), {
						guitarSeq = guitarSeq.add(rec[rIndex].add(modelReps[sectionCount][4] * (1 - ((1 / reps) * index))));
					});

					// if chord randomly choose one of the notes
					if(guitarSeq.last[2] == 0, {
						arg toAdd = [];
						toAdd = toAdd.add(guitarSeq.pop);
						toAdd = toAdd.add(guitarSeq.pop);
						toAdd[0][2] = toAdd[1][2];
						toAdd[1][3] = toAdd[0][3];
						toAdd = toAdd.choose;
						guitarSeq = guitarSeq.add(toAdd);
					});
				});

				if(index < (reps - 1), {
					guitarSeq = extendToBeat.value(guitarSeq, 4)[0];
				}, {
					guitarSeq = extendToBeat.value(guitarSeq, 8)[0];
				});

			});

			insertTS.value(guitarSeq, timeStampSectionStart, 0, true);

			timeStampSection = 0;
			timeStampSectionStart = guitarSeq.slice(nil, 2).sum;
			lastDur = initSeq[index + 1][2];
			state = 2;
		});
	});
	[guitarSeq, sectionSeq, timeSigInsSeq]
};

// add the high note part
finalizeAccompHigh = {arg sectionSeq;
	var accompHighSeq, timeStamp, subSecType, modelAccomp;
	accompHighSeq = [];
	timeStamp = 0;
	subSecType = 0;

	modelAccomp = sectionSeq.size.collect({
		[
			//short probability
			1.5 + 0.5.rand2,
			//rest probablity
			3 + 1.0.rand2,
			//short note average
			20 + 5.rand2,
			//short note range
			5 + 3.rand2,
			//long note average
			50 + 10.rand2,
			//long note range
			10 + 5.rand2,
			//rest average
			40 + 10.rand2,
			//rest range
			5 + 5.rand2,
			//internote space (short rest)
			6.rand
		]
	});

	sectionSeq.do({arg subSecData, subSecIndex;
		var subSecEnd, freq, noRestCount, shortCount;
		subSecEnd = subSecData[0];
		freq = if(subSecIndex.even, {50.midicps * 8},  {50.midicps * 8 * 6/5});
		if(subSecData.last, {subSecType = ((subSecType + 1) % 2)});
		noRestCount = 0;
		shortCount = 0;
		while({timeStamp < subSecEnd}, {
			var dur, sus, isShort, insertRest;

			isShort = case
			{shortCount == 0} {true}
			{shortCount < 3} {[true, false].wchoose([modelAccomp[subSecIndex][0], 1].normalizeSum)}
			{true} {false};

			insertRest = [true, noRestCount > 3].wchoose([modelAccomp[subSecIndex][1], 1].normalizeSum);

			if(isShort, {
				sus = (modelAccomp[subSecIndex][2] + modelAccomp[subSecIndex][3].rand2).round(2);
				shortCount = shortCount + 1;
			}, {
				sus = (modelAccomp[subSecIndex][4] + modelAccomp[subSecIndex][5].rand2).round(2);
				shortCount = 0;
			});

			if(insertRest, {
				dur = sus + (modelAccomp[subSecIndex][6] + modelAccomp[subSecIndex][7].rand2).round(2);
				noRestCount = 0;
			}, {
				dur = sus + 2 + modelAccomp[subSecIndex][8].rand.round(2);
				noRestCount = noRestCount + 1;
			});

			if((timeStamp + dur) < subSecEnd, {
				accompHighSeq = accompHighSeq.add([freq, dur, sus.clip(0, dur)]);
			}, {
				var remainder;
				remainder = (subSecEnd - timeStamp);
				sus = if(remainder > 10, {((remainder - 10).rand + 8).round(2)}, {0});
				dur = (remainder + 10.rand).clip(2, 1000).round(2);
				accompHighSeq = accompHighSeq.add([freq, dur, sus]);
			});
			timeStamp = timeStamp + dur;
		});
	});
	accompHighSeq
};


// add the low note part
finalizeAccompLow = {arg guitarSeq, sectionSeq;
	var accompLowSeq, durAccum, lastTrigVal;
	accompLowSeq = [];
	durAccum = 0;
	lastTrigVal = 0;
	guitarSeq.do({arg item, i;
		var dur, trig, freq1, freq2, finalDur;
		dur = item[2];
		trig = item.last;
		if(lastTrigVal != trig, {
			freq1 = if(trig > -1, {62.midicps / 4 * 3/4}, {62.midicps / 4});
			freq2 = freq1 + if(trig > -1, {trig}, {0});
			finalDur = durAccum;
			accompLowSeq = accompLowSeq.add([freq1, freq2, finalDur]);
			durAccum = 0;
		});
		durAccum = durAccum + dur;
		lastTrigVal = trig;
	});

	accompLowSeq = [accompLowSeq.slice(nil, 0), accompLowSeq.slice(nil, 1), accompLowSeq.slice(nil, 2).integrate].flop;
	sectionSeq.collect({arg section, secIndex;
		if(section[1] == 1, {
			var curTime, secLength;
			curTime= section[0];
			secLength = section[0] - sectionSeq[secIndex - 1][0];
			accompLowSeq = accompLowSeq.add([62.midicps / 8, (62.midicps / 8) + 0, curTime]);
			curTime = curTime - (50.rand + 50).clip(0, (secLength / 3) - 5).round(4).asInteger;
			accompLowSeq = accompLowSeq.add([64.midicps / 8, (64.midicps / 8) + 2 + 1.0.rand2, curTime]);
			curTime = curTime - (50.rand + 50).clip(0, (secLength / 3) - 5).round(4).asInteger;
			accompLowSeq = accompLowSeq.add([65.midicps / 8, (65.midicps / 8) + 4 + 1.0.rand2, curTime]);
		});
		if(section[1] == -1, {
			var curTime = section[0];
			accompLowSeq = accompLowSeq.add([62.midicps / 4, (62.midicps / 4) + 0, curTime]);
		});
	});

	accompLowSeq = accompLowSeq.sort({ arg a, b; a[2] < b[2] });
	accompLowSeq = [accompLowSeq.slice(nil, 0), accompLowSeq.slice(nil, 1),
		accompLowSeq.slice(nil, 2).differentiate.drop(1).add(1)].flop;

	accompLowSeq
};

~genMusicData = {arg seed;
	var initSeq, finalSeqs, guitarSeq, accompHighSeq, accompLowSeq, sectionSeq, timeSigSeq,
	patterns, scoreData, sectionOffsets;

	initSeq = genInitSeq.value(seed);
	finalSeqs = finalizeSeqs.value(initSeq);
	guitarSeq = finalSeqs[0];
	accompHighSeq = finalizeAccompHigh.value(finalSeqs[1].deepCopy.add([finalSeqs[0].slice(nil, 2).sum, -1, false]));
	accompLowSeq = finalizeAccompLow.value(finalSeqs[0], finalSeqs[1]);
	sectionSeq = finalSeqs[1];
	timeSigSeq = finalSeqs[2];

	patterns = ~genPatterns.value(guitarSeq, accompLowSeq, accompHighSeq, sectionSeq);
	scoreData =  ~genScoreData.value(guitarSeq, accompLowSeq, accompHighSeq, timeSigSeq, sectionSeq);
	sectionOffsets = sectionSeq.slice(nil, 0);

	[patterns, scoreData, sectionOffsets]
};
)