(
//~~SYNTHDEF THAT GENERATES THE PIECE
~ostinato_and_interrupt = SynthDef(\ostinato_and_interrupt, {

	// TODO: Replace PlayBuf with PlayBufCF

	//~~~~~~~~~~~~~~~~ARG DECLARATION~~~~~~~~~~~~~~~~
	arg scoreBuf, seed = 20170121, rt = 0, // set score buffer, random seed, and if run in realtime (sets outputs differently)
	percBufDelims = #[0, 1, 2, 3, 4, 5, 6], // set buffers for percussion samples
	guitOpenBufDelims = #[0, 1, 2, 3, 4, 5, 6], // set buffers for open strings
	guitHarmBufDelims = #[0, 1, 2, 3, 4, 5, 6], // set buffers for harmonics
	guitMuteBufDelims = #[0, 1, 2, 3, 4, 5, 6], // set buffers for muted strings
	guitBassBufDelims = #[0, 1, 2, 3, 4, 5, 6]; // set buffers for bass notes

	//~~~~~~~~~~~~~~~~VAR DECLARATION~~~~~~~~~~~~~~~~
	var fund, durUnit, tuning, oBassNotes, oOpenStringNotes; // static vars
	var li, trig, lastoString, lastoDurTemp, switchTrig, lastoStepper; // feedback in
	var env, iEnv, oEnv, currentDur, switch; // switch
	var oTrig, oStepper, oBassNote, oString, oNote, oFreq, oDurTemp, oDur, oStringCounts,
	    oPluckedStrings, oSwitchTrig, oSwitchArm, oSwitchTrigDel; //ostinato
	var startState, endState, endTrig; // start and end states
	var iTrig, iSound, iDur, iString, iEnsemble, iPerc, iPluckedStrings,
	    iSwitchTrig, iSwitchArm, iSwitchTrigDel; // interrupt
	var aDust, aLBuf, aFlicker, aBNoise, aWNoise, aSinBeat, aSinTrig, aFadeIn, aFadeOut; // electronic accompaniment
	var count; // score
	RandSeed.ir(1, seed); // random seed - change for different results

	//~~~~~~~~~~~~~~~~STATIC VARS~~~~~~~~~~~~~~~~
	fund = 38; // fundamental in midi (guitar low d)
	durUnit = 0.2; // minimum durational unit
	tuning = [1/1, 3/2, 2/1, 5/2, 7/2, 4/1]; // tuning of the open strings
	oBassNotes = [10, 9, 8, 3, 2, 0]; // bass notes relative to fundamental in semitones
	oOpenStringNotes = [0, 7, 12, 16]; // open string notes relative to fundamental in semitones (used only for score)

	//~~~~~~~~~~~~~~~~FEEDBACKIN~~~~~~~~~~~~~~~~
	li = LocalIn.kr(6, [0, -1, -1, 0, 0, 1]); // init vals
	trig = li[0] + TDelay.kr(Impulse.kr(0), 12); // start the guitar 12 seconds after electronic accompaniment
	lastoString = li[1]; // get last ostinato string for
	lastoDurTemp = li[2]; // get last duration value (not yet multiplied by durUnit)
	lastoStepper = li[3]; // last position of the ostinato
	switchTrig = li[4]; // trigger to change from ostinato to interrupt
	startState = li[5]; // delay before interrupts can start to occur

	//~~~~~~~~~~~~~~~~SWITCH~~~~~~~~~~~~~~~~
	env = EnvGen.kr(Env.new([0, 1], 60 * 13, \sine), 1 - startState); // env that controls the switching between ostinato and interrupt
	oEnv = 0.25 + (env * 3.75); // chance that ostinato will switch to interrupt over time
	iEnv = 1 + (env * 29); // chance that interrupt will switch to ostinato over time
	currentDur = (Line.kr(0, 60 * 20, 60 * 20) / 0.2).trunc; // time tracker
	switch = Stepper.kr(switchTrig, 0, 0, 1); // state - ostinato or interrupt
	Poll.kr(trig, currentDur, \currentDur); // poll current duration
	Poll.kr(trig, env, \env); // monitor current position of envelope

	//~~~~~~~~~~~~~~~~OSTINATO~~~~~~~~~~~~~~~~
	// mute triggers when off
	oTrig = trig * (switch <= 0);
	// update string counts
	oStringCounts = { arg i;
		var isString = (i <= lastoString) * (lastoString <= i);
		PulseCount.kr(oTrig * isString, Changed.kr(lastoStepper)) } ! 4;
	// step through the bass note of the ostinato once all notes in a cell have been played
	oStepper = Stepper.kr((oTrig * TWChoose.kr(oTrig, [0, 1], [1, 1], 1) * (Mix.new(oStringCounts > 0) >= 3)), 0, 0, 5);
	oBassNote = Select.kr(oStepper, oBassNotes);
	// select duration favoring a change for longer notes to promote flurries of shorter notes
	oDurTemp = TWChoose.kr(oTrig * TWChoose.kr(oTrig, [0, 1], [(((lastoDurTemp <= 0) * 0.75) + 1) * (lastoDurTemp > 5), 1], 1),
		[1, 2, 3, 4, 5, 6, 7], Select.kr(oStepper < 3, [[4, 3, 2, 2, 2, 1, 1], [2, 3, 3, 3, 2, 1, 1]]), 1);
	// add jitter to duration (if this is odd unfortunately a rounding error is produced)
	oDur = 2 * (oDurTemp + TIRand.kr(0, Select.kr(oDurTemp < 3, [2, 0] ), oTrig));
	// select string (always oBassNote if oBassNote has been stepped to next) and promote change on shorter notes
	oString = TWChoose.kr(oTrig,
		Select.kr(oStepper < 3, [[0, 1, 2], [1, 2, 3]]),
		Select.kr(Changed.kr(oStepper), [[4, 3, 3] * (1 / pow(Select.kr(oStepper < 3,
			[oStringCounts.drop(-1), oStringCounts.drop(1)]) + 1, Select.kr(lastoDurTemp < 2, [0.75, 2]))), [1, 0, 0]]), 1);
	// select note based on string
	oNote = Select.kr((oString - (oStepper < 3)) > 0, [oBassNote, Select.kr(oString, oOpenStringNotes)]);
	// play guitar
	oPluckedStrings = { |i| var string, isString, isStringDel, freq, snd;
		string = 5 - i; // invert string number (since oString 1 is guitar string IV)
		isString = oTrig * (string <= (5 - oString)) * ((5 - oString) <= string); // check if string is triggered
		isStringDel = TDelay.kr(isString, 0.01); // slight delay for envelope
		// play samples and select open string or bass note
		snd = PlayBuf.ar(1, Select.kr(Latch.kr((oString - (oStepper < 3)) > 0, isStringDel),
			[TIRand.kr(Select.kr(oStepper, guitBassBufDelims), Select.kr(oStepper, guitBassBufDelims) - 1, isStringDel),
				TIRand.kr(guitOpenBufDelims[string], guitOpenBufDelims[string + 1] - 1, isStringDel)
		]), Latch.kr(1, isStringDel), isStringDel) * (1 - EnvGen.ar(Env.new([0, 1, 0], [0.01, 0.01]), isString));
		Out.ar(Select.kr(rt, [string - 2, [0, 1]]), snd * (1/3)); // ostinato guitar records to channels 0 - 3
	} ! 4;
	/* karplus strong version - legacy sonification used for auditioning the piece
	oFreq = Select.kr((oString - (oStepper < 3)) > 0, [(oNote + fund).midicps, fund.midicps * Select.kr(oString, tuning)]);
	oPluckedStrings = { arg i;
		var string, isString, snd;
		string = 5 - i;
		isString = oTrig * (string <= oString) * (oString <= string);
		snd = Pluck.ar(WhiteNoise.ar(0.1), isString, 0.2,
			Latch.kr(oFreq.reciprocal, isString), 10, 0);
		Out.ar(Select.kr(rt, [string, [0, 1]]), snd);
	} ! 4;*/
	// endState - envelope has finished but cycles through switches until the final bass note is played
	endState = Latch.kr(1, trig * (env >= 1) * (switch > 0) * (oStepper <= 2) * (oStepper >= 2));
	endTrig = trig * endState * (oStepper >= 5);
	FreeSelf.kr(TDelay.kr(endTrig, (oDur * durUnit) + 10));
	// trigger switch and allow notes to pass until next 5 second interval (so interrupt always starts on a common multiple of 4 and 5)
	oSwitchTrig = (startState <= 0) * (endState <= 0) * oTrig * (PulseCount.kr(Changed.kr(oStepper), Changed.kr(switch)) > (4 - oEnv)) *
	    TWChoose.kr(oTrig, [0, 1], [1 - (env >= 1), oEnv], 1);
	oSwitchArm = PulseCount.kr(oSwitchTrig, switchTrig); // switch trig armed
	oSwitchTrigDel = 20 - (currentDur % 20); // count down to switch
	oDur = Select.kr(oSwitchArm <= 0, [Clip.kr(oDur, 0, oSwitchTrigDel), oDur]); // clip dur if past switch
	oSwitchTrig = oTrig * (oDur >= oSwitchTrigDel) * (oSwitchArm > 0); // trigger the switch
	Poll.kr(oTrig, oDur, \oDur); // monitor duration of the ostinato note

	//~~~~~~~~~~~~~~~~Interrupt~~~~~~~~~~~~~~~~
	// mute triggers when off
	iTrig = trig * switch;
	// select sound type: mute, harmonic, or open string (make first interuption not have percussion)
	iSound = TWChoose.kr(iTrig * TWChoose.kr(iTrig, [0, 1], [1, 2], 1), [0, 1, 2],
		Select.kr(PulseCount.kr(Changed.kr(switch)) > 1, [[1, 1, 0], [1, 1, 1]]), 1);
	// select duration
	iDur = Select.kr(iSound < 2,
		[1, TChoose.kr(iTrig * TWChoose.kr(iTrig, [0, 1], [1, 1], 1), [1, 2, 3])]);
	// select string as a stepper occasionally changing how many strings are skipped
	iString = Stepper.kr(iTrig, 0, 0, 5, TWChoose.kr(iTrig, [1, 2, 3], [3, 2, 1], 1));
	// play ensemble
	iEnsemble ={ |i| var string, isString, harm, freq, rel, fade, snd;
		string = 5 - i; // invert string number (since iString 1 is guitar string IV)
		isString = iTrig * (string <= (5 - iString)) * ((5 - iString) <= string); // check if string is triggered
		harm = TRand.kr(1 + iSound, iSound * 5 + 1, isString); // set based on open string or harmonic
		freq = fund.midicps * tuning[i] * harm; // calculate freq
		rel = Select.kr(switch, [2, 0]); // set release
		fade = EnvGen.kr(Env.asr(releaseTime: rel), // gate if sound is mute or with switch
			Latch.kr(Select.kr(iSound < 2 * switch, [0, 1]), isString + (Changed.kr(switch) * (1 - switch))));
		snd = SinOsc.ar(Latch.kr(freq, isString), 0, fade * (1/harm)); // simple sine tone with amp 1/harmonic number
		Out.ar(Select.kr(rt, [string + 16, [0, 1]]), snd * (1/12)); // interrupt ensemble records to channels 16 - 21
	} ! 6;
	iPerc = { |i| var string, isString, isStringDel, freq, snd;
		string = 5 - i; // invert string number (since iString 1 is guitar string IV)
		isString = iTrig * (string <= (5 - iString)) * ((5 - iString) <= string) * (iSound >= 2); // check if sound is percussion / mute
		isStringDel = TDelay.kr(isString, 0.01); // slight delay for envelope
		// play samples
		snd = PlayBuf.ar(1, TIRand.kr(percBufDelims[string], percBufDelims[string + 1] - 1, isStringDel),
			Latch.kr(1, isStringDel), isStringDel) * (1 - EnvGen.ar(Env.new([0, 1, 0], [0.01, 0.01]), isString));
		Out.ar(Select.kr(rt, [string + 10, [0, 1]]), snd * (1/2)); // interrupt ensemble records to channels 10 - 15
	} ! 6;
	iPluckedStrings = { |i| var string, isString, isStringDel, snd;
		string = 5 - i; //invert string number (since iString 1 is guitar string IV)
		isString = iTrig * (string <= (5 - iString)) * ((5 - iString) <= string); // check if string is triggered
		isStringDel = TDelay.kr(isString, 0.01); // slight delay for envelope
		// play samples and select open string, harmonic, or muted string
		snd = PlayBuf.ar(1, Select.kr(Latch.kr(iSound, isStringDel),
			[TIRand.kr(guitOpenBufDelims[string], guitOpenBufDelims[string + 1] - 1, isStringDel),
			TIRand.kr(guitHarmBufDelims[string], guitHarmBufDelims[string + 1] - 1, isStringDel),
			TIRand.kr(guitMuteBufDelims[string], guitMuteBufDelims[string + 1] - 1, isStringDel)
		]), Latch.kr(1, isStringDel), isStringDel) * (1 - EnvGen.ar(Env.new([0, 1, 0], [0.01, 0.01]), isString));
		Out.ar(Select.kr(rt, [string + 4, [0, 1]]), snd * (1/3)); // interrupt ensemble records to channels 4 - 9
	} ! 6;
	/* karplus strong version - legacy sonification used for auditioning the piece
	iPluckedStrings =
	{ |i| var string, isString, freq, snd;
	    string = 5 - i;
	    isString = iTrig * (i <= iString) * (iString <= i) * (iSound < 2);
		freq = fund.midicps * tuning[i] *
		    TRand.kr(1 + iSound, iSound * 5 + 1, isString);
		snd = Pluck.ar(WhiteNoise.ar(0.1), isString, 0.2,
			Latch.kr(freq.reciprocal, isString), 10, 0);
		Out.ar(Select.kr(rt, [string + 4, [0, 1]]), snd);
	} ! 6;*/
	// trigger switch and allow notes to pass until next interval of 4 pulses then add 4 pulses (5 pulses = 1 second)
	iSwitchTrig = iTrig * TWChoose.kr(iTrig, [0, 1], [iEnv, 1], 1);
	iSwitchArm = PulseCount.kr(iSwitchTrig, switchTrig); // switch trig armed
	iSwitchTrigDel = 4 - (currentDur % 4); // countdown to switch
	iDur = Select.kr(iSwitchArm <= 0, [Clip.kr(iDur, 0, iSwitchTrigDel), iDur]); // clip dur if past switch
	iSwitchTrig = iTrig * (iDur >= iSwitchTrigDel) * (iSwitchArm > 0); // trigger the switch
	iDur = Select.kr(iSwitchTrig, [iDur, iDur + 4]); // add 4 pulses
	Poll.kr(iTrig, iDur, \iDur); // monitor duration of interrupt note

	//~~~~~~~~~~~~~~~~Electronic Accompaniment~~~~~~~~~~~~~~~~
	aDust = Dust.kr(10); // random triggers
	aLBuf = LocalBuf.new((SampleRate.ir / fund.midicps).trunc); // buffer for harmonic flickering sound
	// fill buf with random bursts of noise
	RecordBuf.ar(PinkNoise.ar(), aLBuf, run: Latch.ar(TWChoose.kr(aDust, [0, 1], [5, 1], 1), aDust + TDelay.kr(aDust, 0.01)));
	// play buf back at a rate equivalent to the bass note in the ostinato, select whether on or off on every ostinato note
	aFlicker = PlayBuf.ar(1, aLBuf, 1, Impulse.ar((fund + oBassNote).midicps), loop: 1) * Latch.ar(
		TWChoose.kr(aDust, [0, 1], [1, 2], 1), aDust) * (1 - switch) * TWChoose.kr(oTrig, [0, 1], [2, 1], 1) * 0.02;
	aBNoise = BrownNoise.ar(0.007) * (1 - switch); // brown noise during ostinato
	aWNoise = WhiteNoise.ar(0.002) * switch; // white noise during interrupt
	// sine tone beating that can change rate (or not) on every ostinato note (around fundamental for ostinato and a fifth down for interrupt
	aSinTrig = TWChoose.kr(oTrig, [0, 1], [1, 1], 1) + Changed.kr(switch);
	aSinBeat = (SinOsc.ar((fund - (switch * 5)).midicps) +
		SinOsc.ar((fund - (switch * 5)).midicps + 0.5 + Latch.ar(TRand.kr(0, 2.5, aSinTrig), aSinTrig))) * 0.05;
	aFadeIn = EnvGen.kr(Env.new([0, 1], [1])); // short fade at start of piece
	aFadeOut = EnvGen.kr(Env.cutoff(10), TDelay.kr(endTrig, oDur * durUnit)); // longer fade at end of piece
	Out.ar(Select.kr(rt, [22, [0, 1]]), aBNoise * aFadeIn * aFadeOut); // brown noise records to channel 22
	Out.ar(Select.kr(rt, [23, [0, 1]]), aWNoise * aFadeIn * aFadeOut); // white noise records to channel 23
	Out.ar(Select.kr(rt, [24, [0, 1]]), aSinBeat * aFadeIn * aFadeOut); // sine beating records to channel 24
	Out.ar(Select.kr(rt, [25, [0, 1]]), aFlicker * aFadeIn * aFadeOut); // harmonic flickering records to channel 25

	//~~~~~~~~~~~~~~~~Score~~~~~~~~~~~~~~~~
	// write score buffer for lilypond transcription
	count = PulseCount.kr(trig);
	BufWr.kr(Select.kr(switch,
		[[switch, oString, oNote, 0, oDur], [switch, iString, 0, iSound, iDur]]), scoreBuf, Select.kr(trig, [-1, count]));
	Poll.kr(trig, count, \scoreCount);

	//~~~~~~~~~~~~~~~~FeedbackOut~~~~~~~~~~~~~~~~
	LocalOut.kr([
		// feedback note trigger
		TDelay.kr(trig * (1 - endTrig), Select.kr(switch, [oDur, iDur]) * durUnit - ControlRate.ir.reciprocal),
		// feedback oString, oDurTemp, and oStepper for counts
		oString,
		oDurTemp - 1,
		oStepper,
		// feedback switchTrig
		TDelay.kr(Select.kr(switch, [oSwitchTrig, iSwitchTrig]), Select.kr(switch, [oDur, iDur]) * durUnit - ControlRate.ir.reciprocal),
		// feedback whether or not in start state
		PulseCount.kr(Changed.kr(oStepper)) <= 10
	]);
});
)


/*
(
// Uncomment to test / play everything in real time - see ~generateData for comments of synth messages
var sample_index = 0, seed = 20170121;
var perc_allocs, guit_open_allocs, guit_harm_allocs, guit_mute_allocs, guit_bass_allocs;
var allocMSGs = {
	arg inFolder;
	var bufAllocs = [], bufDelims = [];
	PathName(inFolder).folders.sort({arg a, b; a.folderName[0..1].asInteger < b.folderName[0..1].asInteger }).collect({
		|folder|
		bufDelims = bufDelims.add(sample_index);
		folder.files.collect({|file|
			bufAllocs  = bufAllocs.add([\b_allocRead, sample_index, file.fullPath]);
			sample_index = sample_index+1;
		});
	});
	bufDelims = bufDelims.add(sample_index);
	[bufAllocs, bufDelims]
};

~dir = thisProcess.nowExecutingPath.dirname;
perc_allocs = allocMSGs.value(~dir +/+ "../samples/percussion/");
guit_open_allocs = allocMSGs.value(~dir +/+ "../samples/strings_open/");
guit_harm_allocs = allocMSGs.value(~dir +/+ "../samples/strings_harmonics/");
guit_mute_allocs = allocMSGs.value(~dir +/+ "../samples/strings_muted/");
guit_bass_allocs = allocMSGs.value(~dir +/+ "../samples/ostinato_bass/");

s.listSendBundle(0,
	perc_allocs[0] ++ guit_open_allocs[0] ++ guit_harm_allocs[0] ++ guit_mute_allocs[0] ++ guit_bass_allocs[0] ++
	[["/b_alloc", sample_index + 1, 10000, 5], ["/d_recv", ~ostinato_and_interrupt.asBytes(s);]]);


Synth.new(\ostinato_and_interrupt, [\scoreBuf, sample_index + 1, \seed, seed, \rt, 1,
	\percBufDelims, perc_allocs[1],
	\guitOpenBufDelims, guit_open_allocs[1],
	\guitHarmBufDelims, guit_harm_allocs[1],
	\guitMuteBufDelims, guit_mute_allocs[1],
	\guitBassBufDelims, guit_bass_allocs[1]
]);
)
*/
