(
//~~FUNCTION THAT GENERATES THE GUI
~generateGUI = {
	var win, clockStringFunc, metronomeStringFunc, metronomeColorFunc, masterView, faderView, helpView, tabs;
	var tabButtonReset, transportButton, mixerButton, helpButton, startPos = 0;
	var partAbbr = ["guitar", "accompHigh", "accompLowLower", "accompLowUpper", "interlude"];
	var trackNames = ["guitar", "high", "low 1", "low 2", "interlude"];
	var partVols, partMutes, partPans;
	var masterMute, masterVol;

	// set initial mixer values
	partVols = [1, 1, 1, 1, 1];
	partMutes = [0, 1, 1, 1, 1];
	partPans = [0, 0, 0, 0, 0];
	masterMute = 1;
	masterVol = 1;

	// these funcs update the elements of the transport panel
	clockStringFunc = {
		arg measure, beat;
		var measureString, beatString, leadSpace;
		measureString = measure.asInteger.asString;
		beatString = beat.asInteger.asString;
		leadSpace = (3 - measureString.size).collect({" "}).join;
		leadSpace ++ measureString ++ "." ++ beatString
	};
	// [-30, -105, -104].asAscii and [-30, -105, -113].asAscii are unicode inverse bullet and normal bullet, respectively
	metronomeStringFunc = { arg beat; if(beat == 1, {[-30, -105, -104].asAscii}, {[-30, -105, -113].asAscii}) };
	metronomeColorFunc = { arg beat; if(beat == 1, {Color.red},{Color.black}) };

	win = Window("Counterfeiting in Colonial Connecticut", Rect(500, 500, 1100, 575), false).front;
	masterView = {
		var updateTransport, updateSection,
		view, generator, transport, countOff, ranSeed, order, tempo, sectionDisplay, clock, metronome, address;

		// this func updates the whole transport panel
		updateTransport = {arg measure, beat;
			clock.string = clockStringFunc.value(measure, beat);
			metronome.stringColor = metronomeColorFunc.value(beat);
			metronome.string = metronomeStringFunc.value(beat);
			{0.75.wait; {metronome.string = ""}.defer}.fork(~tempoClock, quant: 0);
		}.inEnvir;

		// this func handles the movement between sections
		updateSection = {arg shift, stop = true, manualCall = true;
			var runThis;
			runThis = (manualCall || (manualCall.not && ~autoAdvance));
			runThis = runThis && ((~currentSection + shift) < ~sectionOrder.size);
			runThis = runThis && (((~currentSection % 4) == 3) && ~interludes && manualCall.not).not;
			if(runThis, {
				var truncOnly, section, subSection;
				if(~isPlaying, {
					if(stop, {
						~patterns[~sectionOrder[~currentSection]].stop
					})
				});

				truncOnly = case
				{(~currentSection + shift) < 0} {true}
				{(shift < 0) && ~isPlaying} {true}
				{(shift < -1) && ((~currentSection % 4) > 0)} {true}
				{true} {false};

				if(truncOnly.not, {
					~currentSection = (~currentSection + shift).trunc(shift.abs);
				}, {
					~currentSection = ~currentSection.trunc(shift.abs);
				});

				section = ((~sectionOrder[~currentSection] / 4) + 1).asInteger;
				subSection = ((~sectionOrder[~currentSection] % 4) + 1).asInteger;
				sectionDisplay.string = "section: " ++ section.asString ++ "." ++ subSection.asString;
				if(~isPlaying, {
					~play.set(\sel, ~currentSection % 2);
					~patterns[~sectionOrder[~currentSection]].play(~tempoClock, quant: 0);
					if(~interludes && ((~currentSection % 4) == 3) && (~currentSection != (~sectionOrder.size - 1)), {
						var freq1, freq2, tremRate;
						freq1 = (50 + 7.rand2).midicps;
						freq2 = (freq1.cpsmidi + 3.0.rand + 2).midicps;
						tremRate = 50 + 4.0.rand2;
						~interludeTremelo.set(\gate, 1, \amp, 1, \freq1, freq1, \freq2, freq2, \tremRate, tremRate);
					});
					if((~currentSection % 4) == 0, {
						~interludeTremelo.set(\gate, 0);
					});
					if(((~currentSection % 4)) != 0 && ((~currentSection % 4) != 3), {
						~interludeTremelo.set(\gate, 0, \amp, 0);
					});
				}, {
					var measure, beat;
					measure = ~sectionStartMeasure[~sectionOrder[~currentSection]];
					beat = 1;
					updateTransport.value(measure, beat);
				});
			});
		}.inEnvir;

		// these funcs receive messages from the synth
		OSCFunc({ arg msg, time;
			{
				var measure, beat;
				measure = msg[3];
				beat = msg[4];
				updateTransport.value(measure, beat)
			}.inEnvir.defer;
		},'/measureClock' ++ ~hash, s.addr);

		OSCFunc({ arg msg, time; {updateSection.value(1, false, false)}.inEnvir.defer},'/nextSubsection' ++ ~hash, s.addr);

		OSCdef(\externalAdvance ++ ~hash, {arg msg, time; {updateSection.value(1)}.inEnvir.defer},'/nextSubsection', s.addr);

		view = View(win);
		generator = HLayout(
			ranSeed = TextField(view, Rect(10, 10, 10, 20)).string_("20200525"),
			Button(view).states_([["reset seed"]]).action_({ ranSeed.string = "20200525"}.inEnvir),
			Button(view).states_([["random seed"]]).action_({ ranSeed.string = 50000000.rand.asString}.inEnvir),
			Button(view).states_([["generate"]]).action_({
				{~genAll.value(ranSeed.string.asInteger); ~appStatus.string = "status: ready"}.fork(AppClock);
				~appStatus.string = "status: generating"}.inEnvir),
			Button(view).states_([["transcribe"]]).action_({
				{~transcribe.value(~scoreData, ranSeed.string); ~appStatus.string = "status: ready"}.fork(AppClock);
				~appStatus.string = "status: transcribing"}.inEnvir),
			[~appStatus = StaticText(view).string_("status: ready"), stretch: 1], nil);
		transport = HLayout(
			Button(view).states_([["<<", Color.black]]).action_({arg pState; updateSection.value(-4)}.inEnvir),
			Button(view).states_([["<", Color.black]]).action_({arg pState; updateSection.value(-1)}.inEnvir),
			Button(view).states_([["play", Color.black], ["stop", Color.black, Color.grey]]).action_(
				{arg pState;
					if(pState.value == 0, {
						var measure, beat;
						countOff.stop;
						~isPlaying = false;
						~patterns[~sectionOrder[~currentSection]].stop;
						~interludeTremelo.set(\gate, 0);
						measure = ~sectionStartMeasure[~currentSection];
						beat = 1;
						updateTransport.value(measure, beat);
						~interludeTremelo.set(\gate, 0, \amp, 0);
					},{
						countOff = {
							[1, 2, 1, 2].do({arg beat;
								{
									metronome.stringColor = metronomeColorFunc.value(beat);
									metronome.string = metronomeStringFunc.value(beat);
								}.defer;
								0.75.wait;
								{metronome.string = ""}.defer;
								0.25.wait;
							});
							~isPlaying = true;
							~play.set(\sel, ~currentSection % 2);
							~patterns[~sectionOrder[~currentSection]].play(~tempoClock, quant: 0);
							if(~interludes && ((~currentSection % 4) == 3) && (~currentSection != (~sectionOrder.size - 1)), {
								var freq1, freq2, tremRate;
								freq1 = (50 + 7.rand2).midicps;
								freq2 = (freq1.cpsmidi + 3.0.rand + 2).midicps;
								tremRate = 50 + 4.0.rand2;
								~interludeTremelo.set(\gate, 1, \amp, 1, \freq1, freq1, \freq2, freq2, \tremRate, tremRate);
							});
						}.fork(~tempoClock, quant: 0);
					})
				}.inEnvir
			),
			Button(view).states_([[">", Color.black]]).action_({arg pState; updateSection.value(1)}.inEnvir),
			Button(view).states_([[">>", Color.black]]).action_({arg pState; updateSection.value(4)}.inEnvir), nil,
			sectionDisplay = StaticText(win).string_("section: 1.1").font_(Font("Monaco", 70)), nil);
		view.layout_(HLayout(
			[VLayout(
				HLayout(clock = StaticText(win).string_("  1.1").font_(Font("Monaco", 200)),
					StaticText(win).string_("|").font_(Font("Monaco", 200)),
					metronome = StaticText(win).string_([-30, -105, -104].asAscii).font_(Font("Monaco", 300)).stringColor_(Color.red)),
				nil, transport, nil,
				HLayout(
					tempo = TextField(view).string_("90"),
					Button(view).states_([["set tempo"]]).action_({~tempoClock.tempo = tempo.string.asInteger / 60}.inEnvir),
					StaticText(view).string_(" | "),
					Button(view).states_([["auto advance", Color.black], ["auto advance", Color.black, Color.grey]]).action_({
						arg v; ~autoAdvance = if(v.value == 0, {false}, {true});~autoAdvance;
					}.inEnvir).value_(1),
					Button(view).states_([["interludes", Color.black], ["interludes", Color.black, Color.grey]]).action_({
						arg v; ~interludes = if(v.value == 0, {false}, {true})
					}.inEnvir),
					StaticText(view).string_(" | "),
					address = TextField(view, Rect(10, 10, 10, 20)).string_("127.0.0.1:57120"),
					Button(view).states_([["set address:port"]]).action_({
						var addr, ip, port;
						addr = address.string.split($:);
						ip = addr[0];
						port = addr[1].asInteger;
						thisProcess.openUDPPort(port);
						addr = NetAddr(ip, port);
						OSCdef(\externalAdvance ++ ~hash, {arg msg, time; {updateSection.value(1)}.inEnvir.defer},'/nextSubsection', addr);
					}.inEnvir),
					[StaticText(view).string_("  "), stretch: 1]),
				[StaticText(view).string_("  "), stretch: 1],
				HLayout(
					order = TextField(view).string_("1-16"),
					Button(view).states_([["set order"]]).action_({
						~patterns[~sectionOrder[~currentSection]].stop;
						~sectionOrder = order.string.split($,).collect({arg secEntry;
							var bounds;
							bounds = secEntry.split($-).collect({arg item; item.asInteger - 1});
							((bounds.minItem)..(bounds.maxItem)).collect({arg sec;
								(sec.asInteger * 4) + [0, 1, 2, 3]
							});
						}).flat;
						~currentSection = 0;
						updateSection.value(0);
					}.inEnvir),
					[StaticText(view).string_("  "), stretch: 1]),
				[StaticText(view).string_("  "), stretch: 1], generator
	), alignment: \top]))};
	faderView = {
		var view, masterIndicators, trackIndicators, master, tracks;
		view = View(win);
		masterIndicators = {LevelIndicator()} ! 2;
		trackIndicators = {LevelIndicator()} ! 5;

		OSCFunc.new({arg msg; {
			{arg i; masterIndicators[i].value = msg[3 + i].ampdb.linlin(-40, 0, 0, 1)} ! 2}.defer},
		'/masterLevels' ++ ~hash, s.addr);
		OSCFunc.new({arg msg; {
			{arg i; trackIndicators[i].value = msg[3 + i].ampdb.linlin(-40, 0, 0, 1)} ! 5}.defer},
		'/trackLevels' ++ ~hash, s.addr);

		master = HLayout(
			VLayout(
				[HLayout(
					Slider(view).value_(0.8).action_(
						{arg v; masterVol = v.value * 1.25; ~play.set(\masterVol, masterVol)}.inEnvir),
					masterIndicators[0],
					masterIndicators[1]), stretch: 2],
				Button(view).states_([["mute", Color.black], ["mute", Color.black, Color.grey]]).action_(
					{arg v; masterMute = (1 - v.value).abs; ~play.set(\masterMute, masterMute)}.inEnvir),
				StaticText(view).string_("             master             ").align_(\center)
		), nil);
		tracks = {arg part;
			HLayout(
				VLayout(
					HLayout(
						Slider(view).value_(0.8).action_(
							{arg v; partVols[part] = v.value * 1.25; ~play.set(partAbbr[part] ++ "Vol", partVols[part])}.inEnvir),
						trackIndicators[part]),
					Button(view).states_([["mute", Color.black], ["mute", Color.black, Color.grey]]).action_(
						{arg v; partMutes[part] = (1 - v.value).abs; ~play.set(partAbbr[part] ++ "Mute", partMutes[part])}.inEnvir).value_(
						{if(part == 0, {1}, {0})}.value),
					StaticText(view).string_("pan").align_(\center),
					Knob(view).value_(0.5).action_(
						{arg v; partPans[part] = v.value * 2 - 1; ~play.set(partAbbr[part] ++ "Pan", partPans[part])}.inEnvir),
					StaticText(view).string_(trackNames[part]).align_(\center)
				),
				nil)
		} ! 5;
		view.layout_(HLayout(master, nil, *tracks))};
	helpView = {
		StaticText(win).string_(File.readAllString(~dir +/+ "cicc_readme.scd"));
	};
	tabButtonReset = {transportButton.value = 1; mixerButton.value = 1; helpButton.value = 1};
	win.layout = VLayout(
		HLayout(
			HLayout(
				[
					transportButton = Button().states_([["transport", Color.white, Color.grey], ["transport", Color.black]]).action_(
						{tabButtonReset.value; transportButton.value = 0; tabs.index = 0 }.inEnvir).value_(0), stretch: 1
				], [
					mixerButton = Button().states_([["mixer", Color.white, Color.grey], ["mixer", Color.black]]).action_(
						{tabButtonReset.value; mixerButton.value = 0; tabs.index = 1 }.inEnvir).value_(1), stretch: 1
				]
			),
			helpButton = Button().states_([["help", Color.white, Color.grey], ["help", Color.black]]).action_(
				{tabButtonReset.value; helpButton.value = 0; tabs.index = 2 }.inEnvir).value_(1)
		),
		tabs = StackLayout(masterView.value, faderView.value, helpView.value));
};
)