{	TuneLoon for Kontakt by ground
	The best and worst equal temperaments, brought to you by bird
	Kontakt 4.1+ (details below)
	
	https://www.native-instruments.com/ni-tech-manuals/ksp-manual/en/version-history
	Surpisingly, sgn() wasn't added until 7.1.
	There was no ui_text_edit until 5.0, and no real numbers (floats) and their accompanying function (like log) until 5.6, which makes this really annoying.
	User-defined functions were added in 4.1, which makes a log(x) workaround possible, but I don't care to implement a whole fixed-point Taylor series approximation for this.
	User-defined functions don't allow arguments, so you need to define variables before defining the function, then call the function after using on persistence_changed. It's a hassle.
	$MIDI_CHANNEL works in instrument scripts since 4.1. This variable has a range of 0 to 63 because Kontakt supports 4 midi ports, but I can't really use them.
	set_control_par(), get_control_par() and get_ui_id() were added in 4.0.
	
	This script uses custom UI positioning code. The way it works doesn't make much sense and requires a lot of trial and error.
	Initially, get_control_par(get_ui_id($firstLabel), $CONTROL_PAR_POS_X) is 0.
	But the label moves when you use set_control_par(get_ui_id($firstLabel), $CONTROL_PAR_POS_X), 0).
	Somehow, get_control_par(get_ui_id($firstLabel), $CONTROL_PAR_POS_X) is STILL 0.
	set_ui_color() wasn't added until 5.6.
	
	Sometimes kontakt 4 loses the ability to update ui elements in any call besides init, and I have to reload the plugin.
	This problem is specific to an instance of the plugin in the DAW, so I can have a broken one, add a new one, save and reload the file, and the broken one is still broken and the new one still works.
}
on init
	declare @version
	@version := "TuneLoon K0.1"
	
	set_script_title(@version)
	set_ui_height(6)
	message("")
	
	declare %posConst[6] := (23, 0, 572, 18, 0, 0)
	{	constants for the custom positioning code
		default appears to be (66, 2, 552, 21, 5, 3) with the width split into 6
		0: x-origin px
		1: y-origin px
		2: total width px (looks nice as a multiple of the width fraction denominator in the positioning code)
		3: row height px
		4: x-padding px
		5: y-padding px
	}
	
	declare !notes12[12]
	!notes12[0] := "C"
	!notes12[1] := "C#"
	!notes12[2] := "D"
	!notes12[3] := "Eb"
	!notes12[4] := "E"
	!notes12[5] := "F"
	!notes12[6] := "F#"
	!notes12[7] := "G"
	!notes12[8] := "G#"
	!notes12[9] := "A"
	!notes12[10] := "Bb"
	!notes12[11] := "B"
	declare !midiNoteNames[128]
	declare $i := 0
	while ($i < 128)
		!midiNoteNames[$i] := !notes12[$i mod 12] & (($i / 12) - 1)
		inc ($i)
	end while
	
	declare ui_label $versionLabel (1, 1)
	set_text($versionLabel, @version & " © GroundFault Corporation")
	hide_part($versionLabel, $HIDE_PART_BG)
	set_control_par(get_ui_id($versionLabel), $CONTROL_PAR_POS_X, 425)
	set_control_par(get_ui_id($versionLabel), $CONTROL_PAR_WIDTH, 200)
	set_control_par(get_ui_id($versionLabel), $CONTROL_PAR_POS_Y, 240)
	
	{UI IDs are assigned sequentially, so the order of the declarations here is vital}
	
	{Rf0} declare ui_label $refLabel (1, 1)
	
	{Rf1} declare ui_value_edit $refNote (0, 127, 1)
	set_text($refNote, "MIDI #")
	set_control_help($refNote, "MIDI note number of the reference frequency.")
	$refNote := 69
	make_persistent($refNote)
	
	{Rf2} declare ui_value_edit $refDetune (-50000, 50000, 1000) {it will only show a maximum of 3 digits after the decimal point}
	set_text($refDetune, "Detune ¢")
	set_control_help($refDetune, "Reference frequency detune in cents.")
	$refDetune := 0
	make_persistent($refDetune)
	
	{Rf0*}
	read_persistent_var($refNote)
	set_text($refLabel, "Reference Note: " & !midiNoteNames[$refNote])
	
	{Et0} declare ui_label $etLabel (1, 1)
	set_text($etLabel, "Equal Temperament")
	
	{Et1} declare ui_value_edit $edx (1, 128, 1)
	set_text($edx, "Divisions")
	set_control_help($edx, "Number of equal divisions of some equave.")
	$edx := 24
	make_persistent($edx)
	
	{Et2} declare ui_value_edit $eq (0, 4800000, 1000)
	set_text($eq, "Equave ¢")
	set_control_help($eq, "Equivalence interval in cents.")
	$eq := 1200000
	make_persistent($eq)
	
	{Et3} declare ui_value_edit $suFactor (1, 16, 1)
	set_text($suFactor, "Subunit Factor")
	set_control_help($suFactor, "Multiple of the existing equal temperament, for higher resolution using MIDI channels.")
	$suFactor := 3
	make_persistent($suFactor)
	
	{Et4} declare ui_label $resultLabel (1, 1)
	read_persistent_var($edx)
	read_persistent_var($suFactor)
	set_text($resultLabel, $edx & "*" & $suFactor & " = " & $edx*$suFactor)
	
	{Di0} declare ui_button $dtInvalidButton
	set_text($dtInvalidButton, "Detune Invalid Notes")
	set_control_help($dtInvalidButton, "If selected, notes with pitches outside the standard MIDI range (C-1 to G9) will be heavily detuned in order to play correctly. Otherwise, they will be ignored.")
	$dtInvalidButton := 1
	make_persistent($dtInvalidButton)
	
	{Th0} declare ui_label $midiTransLabel (1, 1)
	set_text($midiTransLabel, "MIDI Channel Transpositions")
	
	{Th1} declare ui_button $genPresetButton
	set_text($genPresetButton, "Preset from SF")
	set_control_help($genPresetButton, "Generate preset MIDI channel configuration based on the current subunit factor.")
	
	{Th2} declare ui_button $clearButton
	set_text($clearButton, "Clear")
	set_control_help($clearButton, "Clear MIDI channel configuration.")
	
	{Tc0} declare ui_label $chLabel (1, 1)
	set_text($chLabel, "Channel:")
	
	{Te0} declare ui_label $eqLabel (1, 2) {increasing the default height makes the label black}
	set_text($eqLabel, "%    EQUAVES    %")
	add_text_line($eqLabel, "%                      %")
	add_text_line($eqLabel, "%                      %")
	add_text_line($eqLabel, "%                      %")
	add_text_line($eqLabel, "%                      %")
	add_text_line($eqLabel, "%                      %")
	
	{TeL} declare ui_button $eqShiftLButton
	set_text($eqShiftLButton, "<")
	set_control_help($eqShiftLButton, "Shift values left.")
	{TeR} declare ui_button $eqShiftRButton
	set_text($eqShiftRButton, ">")
	set_control_help($eqShiftRButton, "Shift values right.")
	{TeU} declare ui_button $eqIncButton
	set_text($eqIncButton, "+")
	set_control_help($eqIncButton, "Increment values.")
	{TeD} declare ui_button $eqDecButton
	set_text($eqDecButton, "-")
	set_control_help($eqDecButton, "Decrement values.")
	{TeI} declare ui_button $eqInvButton
	set_text($eqInvButton, "Invert")
	set_control_help($eqInvButton, "Invert values.")
	
	{TeG} declare ui_value_edit $eqGlobalOffset (-8, 8, 1)
	set_text($eqGlobalOffset, "Global")
	set_control_help($eqGlobalOffset, "Global offset in equaves.")
	$eqGlobalOffset := 0
	make_persistent($eqGlobalOffset)
	
	{Ts0} declare ui_label $suLabel (1, 2) {increasing the default height makes the label black}
	set_text($suLabel, "::    SUBUNITS    ::")
	add_text_line($suLabel, "::                       ::")
	add_text_line($suLabel, "::                       ::")
	add_text_line($suLabel, "::                       ::")
	add_text_line($suLabel, "::                       ::")
	add_text_line($suLabel, "::                       ::")
	
	{TsL} declare ui_button $suShiftLButton
	set_text($suShiftLButton, "<")
	set_control_help($suShiftLButton, "Shift values left.")
	{TsR} declare ui_button $suShiftRButton
	set_text($suShiftRButton, ">")
	set_control_help($suShiftRButton, "Shift values right.")
	{TsU} declare ui_button $suIncButton
	set_text($suIncButton, "+")
	set_control_help($suIncButton, "Increment values.")
	{TsD} declare ui_button $suDecButton
	set_text($suDecButton, "-")
	set_control_help($suDecButton, "Decrement values.")
	{TsI} declare ui_button $suInvButton
	set_text($suInvButton, "Invert")
	set_control_help($suInvButton, "Invert values.")
	
	{TsG} declare ui_value_edit $suGlobalOffset (-8, 8, 1)
	set_text($suGlobalOffset, "Global")
	set_control_help($suGlobalOffset, "Global offset in subunits.")
	$suGlobalOffset := 0
	make_persistent($suGlobalOffset)
	
	{Tb0} declare ui_table %eqChTable[16] (3, 6, -8)
	make_persistent(%eqChTable)
	set_control_help(%eqChTable, "Offset in equaves per MIDI channel.")
	
	{Tb1} declare ui_table %suChTable[16] (3, 6, -8)
	make_persistent(%suChTable)
	set_control_help(%suChTable, "Offset in subunits per MIDI channel.")
	
	{edit the first number when the number of elements changes}
	declare %posArgs[29 * 7] := ( ...
	{Rf0}  0,  5, 26,  0, 0,  2, 1, ...
	{Rf1}  0,  5, 26,  0, 0,  3, 1, ...
	{Rf2}  0,  5, 26,  0, 0,  4, 1, ...
	{Et0}  0,  5, 26,  0, 0,  6, 1, ...
	{Et1}  0,  5, 26,  0, 0,  7, 1, ...
	{Et2}  0,  5, 26,  0, 0,  8, 1, ...
	{Et3}  0,  5, 26,  0, 0,  9, 1, ...
	{Et4}  0,  5, 26,  0, 0, 10, 1, ...
	{Di0}  0,  5, 26,  0, 0, 12, 1, ...
	{Th0}  6, 14, 26,  0, 0,  1, 1, ...
	{Th1} 20,  4, 26,  0, 0,  1, 1, ...
	{Th2} 24,  2, 26,  0, 0,  1, 1, ...
	{Tc0}  6,  4, 26,  0, 0,  2, 1, ...
	{Te0}  6, 20, 26,  0, 0,  3, 5, ...
	{TeL} 13,  2, 52,  0, 0,  4, 1, ...
	{TeR} 15,  2, 52,  0, 0,  4, 1, ...
	{TeU} 17,  2, 52,  0, 0,  4, 1, ...
	{TeD} 17,  2, 52,  0, 0,  5, 1, ...
	{TeI} 13,  4, 52,  0, 0,  5, 1, ...
	{TeG} 13,  6, 52,  0, 0,  6, 1, ...
	{Ts0}  6, 20, 26,  0, 0,  8, 5, ...
	{TsL} 13,  2, 52,  0, 0,  9, 1, ...
	{TsR} 15,  2, 52,  0, 0,  9, 1, ...
	{TsU} 17,  2, 52,  0, 0,  9, 1, ...
	{TsD} 17,  2, 52,  0, 0, 10, 1, ...
	{TsI} 13,  4, 52,  0, 0, 10, 1, ...
	{TsG} 13,  6, 52,  0, 0, 11, 1, ...
	{Tb0} 10, 16, 26, -2, 5,  3, 5, ...
	{Tb1} 10, 16, 26, -2, 5,  8, 5)
	{	arguments by "column":
		0: x position fraction N
		1: width fraction N
		2: D
		3: x position offset px
		4: width offset px
		5: row number (0-indexed)
		6: height in rows
	}
	$i := 0
	while ($i < num_elements(%posArgs)/7)
		set_control_par(get_ui_id($refLabel) + $i, $CONTROL_PAR_POS_X,  %posConst[2] * %posArgs[7*$i + 0] / %posArgs[7*$i + 2] + %posArgs[7*$i + 3] + %posConst[0])
		set_control_par(get_ui_id($refLabel) + $i, $CONTROL_PAR_WIDTH,  %posConst[2] * %posArgs[7*$i + 1] / %posArgs[7*$i + 2] + %posArgs[7*$i + 4] - %posConst[4])
		set_control_par(get_ui_id($refLabel) + $i, $CONTROL_PAR_HEIGHT, %posConst[3] * %posArgs[7*$i + 6] - %posConst[5])
		set_control_par(get_ui_id($refLabel) + $i, $CONTROL_PAR_POS_Y,  %posConst[3] * %posArgs[7*$i + 5] + %posConst[1])
		inc($i)
	end while
	
	declare ui_label $ch1  (1, 1)
	declare ui_label $ch2  (1, 1)
	declare ui_label $ch3  (1, 1)
	declare ui_label $ch4  (1, 1)
	declare ui_label $ch5  (1, 1)
	declare ui_label $ch6  (1, 1)
	declare ui_label $ch7  (1, 1)
	declare ui_label $ch8  (1, 1)
	declare ui_label $ch9  (1, 1)
	declare ui_label $ch10 (1, 1)
	declare ui_label $ch11 (1, 1)
	declare ui_label $ch12 (1, 1)
	declare ui_label $ch13 (1, 1)
	declare ui_label $ch14 (1, 1)
	declare ui_label $ch15 (1, 1)
	declare ui_label $ch16 (1, 1)
	
	{now it's time to be careful about order of operations so integer division works right}
	$i := 0
	while ($i < 16)
		set_control_par_str(get_ui_id($ch1) + $i, $CONTROL_PAR_TEXT, $i + 1)
		set_control_par(get_ui_id($ch1) + $i, $CONTROL_PAR_POS_X,  %posConst[2] * (10 + $i mod 16) / 26 + %posConst[0])
		set_control_par(get_ui_id($ch1) + $i, $CONTROL_PAR_WIDTH,  %posConst[2] / 26 - %posConst[4])
		set_control_par(get_ui_id($ch1) + $i, $CONTROL_PAR_HEIGHT, %posConst[3] * 11 - %posConst[5])
		set_control_par(get_ui_id($ch1) + $i, $CONTROL_PAR_POS_Y,  %posConst[3] * (2 + $i / 16) + %posConst[1])
		inc($i)
	end while
	
	{	This lookup table for generating presets specifies how many equaves should go to each subunit offset.
		y-axis: subunit factor
		x-axis: number of channels per offset
		Rules:
			- Offset 0 gets priority
			- Distribution must be symmetrical with respect to offset 0
			- Coprime offsets are sacrificed first because they are conceptually more difficult to use
	}
	declare %presetLookup[16 * 16] := ( ...
	16, {end} 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
	 8, 8, {end}  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
	 6, 5, 5, {end}  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
	 4, 4, 4, 4, {end}  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
	 4, 3, 3, 3, 3, {end}  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
	 3, 2, 3, 3, 3, 2, {end}  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
	 4, 2, 2, 2, 2, 2, 2, {end}  0, 0, 0, 0, 0, 0, 0, 0, 0, ...
	 2, 2, 2, 2, 2, 2, 2, 2, {end}  0, 0, 0, 0, 0, 0, 0, 0, ...
	 2, 1, 2, 2, 2, 2, 2, 2, 1, {end}  0, 0, 0, 0, 0, 0, 0, ...
	 2, 1, 2, 1, 2, 2, 2, 1, 2, 1, {end}  0, 0, 0, 0, 0, 0, ...
	 2, 1, 2, 1, 2, 1, 1, 2, 1, 2, 1, {end}  0, 0, 0, 0, 0, ...
	 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, {end}  0, 0, 0, 0, ...
	 2, 1, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, {end}  0, 0, 0, ...
	 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, {end}  0, 0, ...
	 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, {end}  0, ...
	 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)
	
	{other non-init callback variables}
	declare $buttonWait := 100000
	declare $j
	declare $k
	declare $temp
	declare $newNote
	declare $newTune
end on

on ui_control ($refNote)
	set_text($refLabel, "Reference Note: " & !midiNoteNames[$refNote])
end on
on ui_control ($edx)
	set_text($resultLabel, $edx & "*" & $suFactor & " = " & $edx*$suFactor)
end on
on ui_control ($suFactor)
	set_text($resultLabel, $edx & "*" & $suFactor & " = " & $edx*$suFactor)
end on

on ui_control ($genPresetButton)
	if ($genPresetButton = 1)
		$i := 0 {output index}
		$j := 0 {subunits counter, index for horizontal axis of lookup table}
		$k := 0 {equaves counter}
		$temp := 16 - (%presetLookup[16*($suFactor-1)]/2) {output table index offset}
		while ($i < 16)
			%eqChTable[($temp+$i) mod 16] := $k - (%presetLookup[16*($suFactor-1) + $j]/2)
			%suChTable[($temp+$i) mod 16] := $j
			inc($k) {count through each equave per subunit before moving to the next}
			if ($k >= %presetLookup[16*($suFactor-1) + $j]) {last equave is reached}
				$k := 0
				inc($j) {move to the next subunit}
			end if
			inc($i)
		end while
		wait($buttonWait)
		$genPresetButton := 0
	end if
end on
on ui_control ($clearButton)
	if ($clearButton = 1)
		$i := 0
		while ($i < 16)
			%eqChTable[$i] := 0
			%suChTable[$i] := 0
			inc($i)
		end while
		wait($buttonWait)
		$clearButton := 0
	end if
end on

on ui_control ($eqShiftLButton)
	if ($eqShiftLButton = 1)
		$temp := %eqChTable[0]
		$i := 0
		while ($i < 15)
			%eqChTable[$i] := %eqChTable[$i + 1]
			inc($i)
		end while
		%eqChTable[15] := $temp
		wait($buttonWait)
		$eqShiftLButton := 0
	end if
end on
on ui_control ($eqShiftRButton)
	if ($eqShiftRButton = 1)
		$temp := %eqChTable[15]
		$i := 15
		while ($i > 0)
			%eqChTable[$i] := %eqChTable[$i - 1]
			dec($i)
		end while
		%eqChTable[0] := $temp
		wait($buttonWait)
		$eqShiftRButton := 0
	end if
end on
on ui_control ($eqIncButton)
	if ($eqIncButton = 1)
		$i := 0
		while ($i < 16)
			%eqChTable[$i] := %eqChTable[$i] + 1
			inc($i)
		end while
		wait($buttonWait)
		$eqIncButton := 0
	end if
end on
on ui_control ($eqDecButton)
	if ($eqDecButton = 1)
		$i := 0
		while ($i < 16)
			%eqChTable[$i] := %eqChTable[$i] - 1
			inc($i)
		end while
		wait($buttonWait)
		$eqDecButton := 0
	end if
end on
on ui_control ($eqInvButton)
	if ($eqInvButton = 1)
		$i := 0
		while ($i < 16)
			%eqChTable[$i] := -%eqChTable[$i]
			inc($i)
		end while
		wait($buttonWait)
		$eqInvButton := 0
	end if
end on

on ui_control ($suShiftLButton)
	if ($suShiftLButton = 1)
		$temp := %suChTable[0]
		$i := 0
		while ($i < 15)
			%suChTable[$i] := %suChTable[$i + 1]
			inc($i)
		end while
		%suChTable[15] := $temp
		wait($buttonWait)
		$suShiftLButton := 0
	end if
end on
on ui_control ($suShiftRButton)
	if ($suShiftRButton = 1)
		$temp := %suChTable[15]
		$i := 15
		while ($i > 0)
			%suChTable[$i] := %suChTable[$i - 1]
			dec($i)
		end while
		%suChTable[0] := $temp
		wait($buttonWait)
		$suShiftRButton := 0
	end if
end on
on ui_control ($suIncButton)
	if ($suIncButton = 1)
		$i := 0
		while ($i < 16)
			%suChTable[$i] := %suChTable[$i] + 1
			inc($i)
		end while
		wait($buttonWait)
		$suIncButton := 0
	end if
end on
on ui_control ($suDecButton)
	if ($suDecButton = 1)
		$i := 0
		while ($i < 16)
			%suChTable[$i] := %suChTable[$i] - 1
			inc($i)
		end while
		wait($buttonWait)
		$suDecButton := 0
	end if
end on
on ui_control ($suInvButton)
	if ($suInvButton = 1)
		$i := 0
		while ($i < 16)
			%suChTable[$i] := -%suChTable[$i]
			inc($i)
		end while
		wait($buttonWait)
		$suInvButton := 0
	end if
end on

{an assignment is the only way I've found to make the tables snap to integers}
on ui_control (%eqChTable)
	%eqChTable[0] := %eqChTable[0]
end on
on ui_control (%suChTable)
	%suChTable[0] := %suChTable[0]
end on

on note
	{	$temp (millicents from $refNote, not counting octave transposition) is a 32-bit signed integer, -2147483648 to 2147483647.
		Division must be the last operation done to maintain precision, but multiplying too much risks integer overflow, so the expression is a sum of quotients.
	}
	$temp := $refDetune ...
	+ ($eq * ($EVENT_NOTE-$refNote) / $edx)										{edx note} ...
	+ ($eq * ($eqGlobalOffset + %eqChTable[$MIDI_CHANNEL]))						{equave alteration} ...
	+ ($eq * ($suGlobalOffset + %suChTable[$MIDI_CHANNEL]) / ($suFactor*$edx))	{subunit alteration}
	
	$newNote := $refNote + ($temp/100000)
	$newTune := $temp mod 100000
	
	if ($newNote < 0)
		if ($dtInvalidButton = 1)
			$newTune := $newTune + ($newNote*100000)
			$newNote := 0
		else
			ignore_event($EVENT_ID)
			exit
		end if
	end if
	
	if ($newNote > 127)
		if ($dtInvalidButton = 1)
			$newTune := $newTune + (($newNote-127)*100000)
			$newNote := 127
		else
			ignore_event($EVENT_ID)
			exit
		end if
	end if
	
	change_note($EVENT_ID, $newNote)
	change_tune($EVENT_ID, $newTune, 0)
end on