User File #639020551590702241

Upload All User Files

#639020551590702241 - TAStudio Output Logger Framework v0.95

TAStudioLogger.lua
134 downloads
Uploaded 12/23/2025 2:52 AM by TASeditor (see all 192)
A framework for your writing your own script to streamline logging memory values and outputting them in TAStudio and saving and loading them with branches as well as script starting and closing.
Version 0.95, not fully done yet. Runs in Bizhawk 2.11

Not working

Branch action undo and branch reordering, it messes up the files and logs.

Features

GUI

It consists of a dropdown list with the branches and the time difference compared to the current section start, a number below 0 means the branch is faster than the current input.
The list is opened by clicking the '▲'-button and clicking on an item from the list selects the given branch as the branch to compare against.
Next to it are '-' and '+'-button to just decrement and increment the branch index by 1.
The 'Best'-button selects the branch with the shortest time between the section start marker and the section end marker, with the same notes as the the current section markers.
Finally the text next to it is the current section start markers text with the time difference to the selected branch at the end, a value below 0 means the current input is faster.

Sections

Sections are marker that don't start its note with "-" or aren't empty. Name the markers individualy - like "Level 1 Key", "Level 2 Key" and so on, or the section find algorithm might fail.
The sections are used to offset the index for relative branch comparison and to display the time difference.

Logs

A log consists of its name, a function how to retrieve the logs values from games memory and a list containing each logged value.

Branch Logs

When saving a branch the current values are saved into a branch log. And loading a branch loads the branch logs values into the current log.

Old Logs

The old logs are the logs with the values from the last input change, which means the old logs are only updated with current logs values when playing back over ungreenzone frames.
It also gets updated by loading branches.

Printers

Those define how to output the values from the logs into TAStudio cells as text and as color.

How to write a script

Since this is a framework, it is not meant to be run in the Lua Console, instead you need to write your own script.
It is surpsingly easy to write a script!
Firstly start from this:
require("TAStudioLogger") -- Or navigate with relatives paths to to the folder where TAStudioLogger is
-- Write your loggers below here:

-- Write your printers below here:

InitializeLogger() -- Needed to load files and setting up variables and callbacks

while true do
	
	UpdateLogger() -- Logs values
	
	emu.yield()
end

Adding a logger

The Loggers take the form "AddLogger(<loggername>, <addressexpression>)".
'loggername' is just a string. It's the name of the logger to be used in the AddPrinter function.

'addressexpression' is either a function that returns a number - in most cases from a memory.read_.. function,
a number of the adress (returns 1 byte unsigned only),
or a string with shorthand expression: "<size>@<address>",
where 'size' is the size of the address (same as in memory.read_.. functions for Bizhawk) and 'address' is the memory address to log.
There are string shorthand form expressions for:
  • "<addr1>+<addr2>" for adding two values - value1+value2,
  • "<addr1>#<addr2>" for multiplying the first value by 256 and then add the second value - value1*256+value2,
  • "<addr1>+<addr2>#<addr3>" for adding the first two values, multiplying by 256, then add the third value - (value1+value2)*256+value3

Examples:
AddLogger("XSpeed", function() return memory.read_s16_le(0x000D56) end)
AddLogger("MidAirFlag", 0x000D7B)
AddLogger("YSpeed", "s16_le@0x000D5A")
AddLogger("XPosition", "u16_le@0x000C29+u8@0x000D61#u8@0x000D50")

Adding a printer

Printers are written as follows: "AddPrinter(<columnname>, <valueexpression>[, <colorexpression>][, <textexpression>][, <digits>])".
'columnname' is the name of the new column added to TAStudio by the script or an existing button column.

'valueexpression' can be
  • just a string of the name of one of your Loggers,
  • a function that takes 'index' as a parameter and returns a number, like:
function(index) return GetBranchLogValue("XPosition", index) end.%%%
  • or a shorthand form string like "<shorthand>,<logname>[,<norm>]",
    where 'shorthand' is the function. It can be of the following:
    • "diff" for 'Difference' function, difference to last frame; e.g.: "diff,XVel"
    • "branchval" for 'GetBranchLogValue' function, value from the log in the selected branch; e.g.: "branchval,XVel"
    • "branchdiff" for 'BranchDifference' function, difference to last frame in the selected branch log; e.g.: "branchdiff,XPos"
    • "branchcomp" for 'BranchCompare' function, subtracts the value in the branch log from the current log; e.g.: "branchcomp,XPos"
    • "relbranchcomp" for 'RelativeBranchCompare' function, does BranchCompare, but with marker offsets, if a marker with the same note is found in the branch; e.g.: "relbranchcomp,XPos"
    • "oldval" for 'GetOldLogValue' function, value from previous playback, the values also change from branch loading; e.g.: "oldval,XVel"
    • "olddiff" for 'OldLogDifference' function, difference to last value from the previous playback; e.g.: "olddiff,XPos"
    • "oldcomp" for 'OldLogCompare' function, subtracts the value in the old log from the current log; e.g.: "oldcomp,XPos"
'logname' is the name of one of your logs, so is the optional parameter 'norm'.
You can normalize - multiply the return value of above functions by the sign of 'norm' - by adding a 3rd parameter, e.g.: "branchcomp,XPos,XVel"

'colorexpression' can be
  • an number 0xAARRGGBB, a string "#AARRGGBB" expressing a color value or from the predefined Colors table - Colors.white, gray, red, green, blue, yellow, magenta, cyan, orange, purple.
  • 'nil' to don't color the cell and use the default TAStudio colors.
  • a table with log values as the key and a color as the value, e.g.:
colortable = { }
colortable[1] = 0xFFE0E0E0
colortable[3] = 0xFFE04090
colortable[8] = 0xFFFF1050
  • a function that takes 'value', 'index' as parameters and returns a color, e.g.:
function(value, index) if value == 15 then return Color(index, 0xFF8080FF ) end end
  • or the shorthand form string in the form "<condition[,param]>;<function[,params]>;<defaultcolor>". The functions are
    • ColorGradient function, "gradient,<maxvalue>[,<startcolor>][,<endcolor>][,<minvalue>][,<exponent>]"
      for example "gradient,20,0xFFFF0000,0xFF00FF00,1.15,5", use "gradientabs" for absolute values,
    • "table,<tablename>" e.g.: "table,colortable". The table must be global.
Additionally another parameter 'defaultcolor' can be passed in the shorthand form string to decide which color to use when the returned value is nil, e.g.: "gradient,50;0xFFFFEEEE".

'textexpression' is optional,
  • if left out or passed nil as the parameter it displays the value of the 'valueexpression',
  • a string that is printed, useful with the conditional expression further down,
  • a table with log values as the key and a color as the value, e.g.:
texttable = { }
colortable[1] = "stand"
colortable[3] = "walk"
colortable[8] = "jump"
  • a function that takes 'value' as a parameter and 'index' optionally and returns a string, like:
function(value) return value < 15 and "invincible" or "" end
  • a shorthand form string in the form of "<condition[,param]>;<function[,param]>"
    • "modform[,param]" to display the log values as <value>/<param>..":"..<value>%<param>. If left out 'param' is 256
    • "modformh[,param]" to display modform as hexadecimal,
    • "modformdh[,param]" to display only the remainder as hexadecimal, those functions have an optional parameter for the divisor, e.g.: "modformh,16"
    • "hex[,param]" to display the value as hexadecimal, with an optional parameter for the size, e.g.: "hex,4".
    • "table,<tablename>" e.g.: "table,exttable". The table must be global.
  • or pass in 'false' as a boolean to display no text.
Text wont be displayed in button columns.

Furthermore both 'colorexpression' and 'textexpression' as a shorthand form string can have an additional expression at the beginning to have a condition when to print text or color the cell.
Options for that are:
  • "onchange" to output when the value from the 'valueexpression' has changed sinced the last frame.
  • "when[,param]" to output when the value from the 'valueexpression' is equal to 'param', default 0.
  • "whennot[,param]" to output when the value from the 'valueexpression' is not equal to 'param', default 0.
  • "whenabove[,param]" to output when the value from the 'valueexpression' is above 'param', default 0.
  • "whenbelow[,param]" to output when the value from the 'valueexpression' is below 'param', default 0.
When one of those expression used alone for the textexpression shorthand form, it will display the value from valueexpression under the given condition. Like those:
  • "when,1;0xFF8040FF;0xFFFFFFFF" colors the cell when the value is 1 otherwise the cell is colored white.
  • "whenabove;dmg" prints "dmg" in the cell when the value is above 0.
  • "whennot" prints the values from 'valueexpression' when the value is not 0.

Examples:
AddPrinter("XVel", "XVel", "gradientabs,704,0xFFFF0000,0xFF00FF00,0,1.5", nil, 4)
AddPrinter("dXV", "diff,XSpeed,XSpeed", "whennot;gradient,64,0xFFFF0000,0xFF00FF00,-64;0xFFFFFFFF", "onchange" ,3)
AddPrinter("P1 Y", "WeaponsOnScreen", weaponcolors)

A finished script might look like this: UserFiles/Info/639020550928746688

-- TAStudio Logger Framework
-- version 0.95, BizHawk 2.11

-- Not to be run in the Lua Console!


----------------
-- How to use --
----------------

-- With this script you can log values from RAM and display them as text and color in TAStudio.
-- It is also possible to compare values with values in branches or the previous playback and even offset the branch comparision by using markers.

-- In the status bar at the bottom it displays the branch you want to compare against, with an dropdown menu with a branch to select.
-- The number in the dropdown indicates how much faster that branch compared to the current inputs, a number lower than 0 means that branch is faster and vice versa.
-- No number means, the current section is not found in that branch.

-- Next to it are 3 buttons, two to change the selected branch index by 1 
-- and the "Best" button which tries to find a branch with the shortest section for the section currently worked on.

-- Sections are markers, that don't start with "-" or have an empty text. 
-- The section start is the first marker upwards from the current framecount, that fulfils these conditions.
-- The section end is the one downwards that fulfils these conditions.
-- Name your markers individually, e.g.: "Level 1 Map", "Level 2 Map" and not "Map get" for every level, as it messes up finding the section start and end.

-- Last is the name of the current section start and how much faster or slower that is compared to the branch you compare. 
-- Lower than 0 means you are faster than the branch and vice versa.

-- The logs are saved and loaded as branches are saved and loaded, as well by closing and starting the script. 
-- The files are in your movie folder. It won't save to a default.tasproj!

---------------------------
-- How to write a script --
---------------------------

-- 1st.: Write "require("TAStudioLogger")" in the top of your script. 
-- 		 Navigate to the folder where TAStudioLogger.lua sits with relative paths, if both script aren't in the same folder.


-- 2nd.: Add Loggers with e.g. "AddLogger("XVel", "s16_le@0xABCD")"
--		 or "AddLogger("XPos", function () return memory.read_u8(0x1234) + memory.read_u8(0x1254)*16 end)"
--		 or "AddLogger("Flags", 0xAABB)"

-- The Loggers take the form "AddLogger(<loggername>, <addressexpression>)" 

-- 'loggername' is just a string. It's the name of the logger to be used in the AddPrinter function.

-- 'addressexpression' is either a function that returns a number, 
-- a number of the adress (returns 1 byte unsigned only) or a string with shorthand expression: "<size>@<address>", 
-- where 'size' is the size of the address (same as in memory.read_.. functions for Bizhawk) and 'address' is the address to log.
-- There are string shorthand form expressions for:
-- 	"<addr1>+<addr2>" for adding two values - value1+value2,
--	"<addr1>#<addr2>" for multiplying the first value by 256 and then add the second value - value1*256+value2,
--	"<addr1>+<addr2>#<addr3>" for adding the first two values, multiplying by 256, then add the third value - (value1+value2)*256+value3


-- 3rd.: Add Printers by writing for example: "AddPrinter("XVel", "XVel", "gradientabs,100", nil, 4)"
--		 or "AddPrinter("P1 A", function(index) return GetLogValue("Flags", index) & 3 end, "#FFDD7040", "")"
--		 or "AddPrinter("rDX", "relbranchcomp,XPos,XVel", "onchange;gradient,20,0xFFFF0000,0xFF00FF00;0xFFAAAAAA", "whenabove,0;modform", 4)"

-- Printers are written as follows: "AddPrinter(<columnname>, <valueexpression>[, <colorexpression>][, <textexpression>][, <digits>])"

-- 'columnname' is the name of the new column added to TAStudio by the script or an existing button column.

-- 'valueexpression' can be just a string of the name of one of your Loggers as 'loggername', 
-- a function that takes 'index' as a parameter and returns a number.
-- For example: function(index) return GetLogValue("Flags", index) & 3 end

-- or a shorthand form string like "<shorthand>,<logname>[,<norm>]", 
-- where 'shorthand' is the function. It can be of the following:
-- "diff" for 'Difference' function, difference to last frame, e.g.: "diff,XVel"
-- "branchval" for 'GetBranchLogValue' function, value from the log in the selected branch, e.g.: "branchval,XVel"
-- "branchdiff" for 'BranchDifference' function, difference to last frame in the selected branch log, e.g.: "branchdiff,XPos"
-- "branchcomp" for 'BranchCompare' function, subtracts the value in the branch log from the current log, e.g.: "branchcomp,XPos"
-- "relbranchcomp" for 'RelativeBranchCompare' function, does BranchCompare, but with marker offsets, if a marker with the same note is found in the branch, e.g.: "relbranchcomp,XPos"
-- "oldval" for 'GetOldLogValue' function, value from previous playback, the values also change from branch loading, e.g.: "oldval,XVel"
-- "olddiff" for 'OldLogDifference' function, difference to last value from the previous playback, e.g.: "olddiff,XPos"
-- "oldcomp" for 'OldLogCompare' function, subtracts the value in the old log from the current log, e.g.: "oldcomp,XPos"

-- 'name' is the name of one of your logs, so is the optional parameter 'norm'.
-- You can normalize - multiply the return value of above functions by the sign of 'norm' - by adding a 3rd parameter, e.g.: "branchcomp,XPos,XVel"


-- 'colorexpression' can be an number 0xAARRGGBB, a string "#[AA]RRGGBB" expressing a color value or from the predefined Colors table,
-- a table with log values as the key and a color as the value, e.g.: {0xFFE0E0E0, 0xFFE04090, 0xFFFF1050},
-- a function that takes 'value', 'index' as parameters and returns a color, e.g.: function(value, index) if value == 15 then return Color(index, , 0xFF8080FF ) end end
-- or the shorthand form string of the ColorGradient function, "gradient,<maxvalue>[,<startcolor>][,<endcolor>][,<minvalue>][,<exponent>]"
-- for example "gradient,20,0xFFFF0000,0xFF00FF00,1.15,5", use "gradientabs" for absolute values,
-- a string in the form of "table,<tablename>" e.g.: "table,mycolortable".
-- Additionally another parameter can be passed in the shorthand form string to decide which color to use when the returned value is nil, e.g.: "gradient,50;0xFFFFEEEE"

-- 'textexpression' is optional, if left out it displays the value of the 'valueexpression',
-- a string that is printed, useful with the conditional expression further down,
-- a table with log values as the key and a color as the value, e.g.: "{"stand", "walk", "jump"}",
-- a function that takes 'value' as a parameter and 'index' optionally and returns a string "function(value) return value < 15 and "invincible" or "" end,
-- the string "modform" to display the log values as <value>/256..":"..<value>%256; prints '125893' as '491:197', "modformh" to display it as hexadecimal,
-- "modformdh" to display only the remainder as hexadecimal, those functions have an optional parameter for the divisor, e.g.: "modformh,16"
-- the string "hex" to display the value as hexadecimal, with an optional parameter for the size "hex,4".
-- a string in the form of "table,<tablename>" e.g.: "table,mytexttable". The table must be global.
-- or pass in 'false' as a boolean to display no text.
-- Text wont be displayed in button columns.

-- Furthermore both 'colorexpression' and 'textexpression' as a shorthand form string can have an additional expression at the beginning to have a condition when to print text or color the cell.
-- Options for that are:
-- "onchange", "when", "whennot", "whenabove" and "whenbelow", the "when.." conditions have an optional parameter for a number, if empty it defaults to 0. E.g.: "whenabove,16".
-- When one of those expression used alone for the textexpression shorthand form, it will display the value from valueexpression under the given condition.
-- Examples:
-- "when,1;0xFF8040FF;0xFFFFFFFF" colors the cell when the value is 1 otherwise the cell is colored white
-- "whenabove;dmg" prints "dmg" in the cell when the value is above 0

-- 4th.: Add "InitializeLogger()" after setting up all your Loggers and Printers.

-- 5th.: In the default while true loop at the bottom write "UpdateLogger()" and replace "emu.frameadvance()" with "emu.yield()" if necessary.

--------------------------------------------------------------------------------------------------------------------------------------------------------------

--------------
--	Logger  --
--------------

local Logs = { }
local Printers = { }
local branches = { } -- entries with 'values = {}'
local oldLogs = { } -- Contains the values from Logs from the previous playback
local currentmarker = nil -- Marker above or at playback coursor
local currentsection = nil -- First marker upwards, that doesn't start with "-" or hasn't an empty note
local lastmarker = nil -- For getting when marker changes
local markerdiff = 0 -- Offset from currentsection marker to the marker in selectedbranch with the same name
local selectedbranch = 1
local ungreenframe = 0 -- For drawing colors in TAStudio pale
local open = false -- For branch dropdown in status bar
local mousedown = false
local loaderror = false -- Prevents overriding log in case of loading wrong script
local lasteditedframe

local tastudio = tastudio
local memory = memory
local gui = gui
local event = event
local math = math



-- For writing addressexpression as shorthand form
local function AddressExpressionLookup(expr)
 
	if string.match(expr, "^float_[bl]e@0x%x+$") == expr 
	then local s = bizstring.split(expr, "@")
		 local addr = tonumber(s[2])
		 
		 if s[1] == "float_le" then return function () return memory.readfloat(addr) end
		 elseif s[1] == "float_be" then return function () return memory.readfloat(addr, true) end
		 else console.log("WARNING: Invalid shorthand addressexpression form "..expr)
			  return
		 end
		 
	else local readfuncs = {}
		 local n = 0
		 
		 local readfuncpattern = "[us][813][62]?_?[bl]?e?@0x%x+"
		 
		 for w in string.gmatch(expr, readfuncpattern) do
			 local s = bizstring.split(w, "@")
			 local addr = tonumber(s[2])
			 n = n + 1
			 
			 if s[1] == "u8" then table.insert(readfuncs, function () return memory.read_u8(addr) end)
			 elseif s[1] == "s8" then table.insert(readfuncs, function () return memory.read_s8(addr) end) 
			 elseif s[1] == "u16_le" then table.insert(readfuncs,  function () return memory.read_u16_le(addr) end)
			 elseif s[1] == "u16_be" then table.insert(readfuncs,  function () return memory.read_u16_be(addr) end)
			 elseif s[1] == "s16_le" then table.insert(readfuncs,  function () return memory.read_s16_le(addr) end)
			 elseif s[1] == "s16_be" then table.insert(readfuncs,  function () return memory.read_s16_be(addr) end)
			 elseif s[1] == "u32_le" then table.insert(readfuncs,  function () return memory.read_u32_le(addr) end)
			 elseif s[1] == "u32_be" then table.insert(readfuncs,  function () return memory.read_u32_be(addr) end)
			 elseif s[1] == "s32_le" then table.insert(readfuncs,  function () return memory.read_s32_le(addr) end)
			 elseif s[1] == "s32_be" then table.insert(readfuncs,  function () return memory.read_s32_be(addr) end)
			 end
		 end
		 
		 if #readfuncs ~= n -- Atleast one function shorthand has a misspelling, like "u36_le"
		 then console.log("WARNING: Invalid shorthand addressexpression form "..expr)
			  return
		 end
		
		 if string.match(expr, "^"..readfuncpattern.."+"..readfuncpattern.."#"..readfuncpattern.."$") == expr
		 then return function() return (readfuncs[1]() + readfuncs[2]()) * 256 + readfuncs[3]() end -- (1st + 2nd)*256 + 3rd
		 elseif string.match(expr, "^"..readfuncpattern.."+"..readfuncpattern.."$") == expr
		 then return function() return readfuncs[1]() + readfuncs[2]() end -- 1st + 2nd
		 elseif string.match(expr, "^"..readfuncpattern.."#"..readfuncpattern.."$") == expr
		 then return function() return readfuncs[1]()*256 + readfuncs[2]() end -- 1st*256 + 2nd
		 elseif string.match(expr, "^"..readfuncpattern.."$") == expr
		 then return function() return readfuncs[1]() end -- 1st
		 else console.log("WARNING: Invalid shorthand addressexpression form "..expr)
			  return
		 end
	end

end

--[[Adds a logger to the Logs table
	Addressexpression needs to be a number, a string or a function which takes no argument and returns a number.
	The value from that expression will be stored in the Logs[name].values for each frame.]]
function AddLogger(name, addressexpression)

	if Logs[name] ~= nil
	then console.log("WARNING: "..name.." is already logged.")
		 return
	end
	
	if type(addressexpression) == "number"
	then local expr = addressexpression
		 addressexpression = function () return memory.read_u8(expr) end
	elseif type(addressexpression) == "string"
		then local expr = AddressExpressionLookup(addressexpression)
			 if expr == nil
			 then return 
			 else addressexpression = expr
			 end
	elseif type(addressexpression) ~= "function"
		then console.log("Address needs to be a number, a string or a function.")
	end
	
	Logs[name] = {expression = addressexpression,
				  values = {}}
				  
	oldLogs[name] = {values = {}}
				  
end

-- For writing valueexpression string shorthand form
local function ValueExpressionLookup(expr)

	local params = bizstring.split(expr, ",")
	local func
	
	if params[1] == "branchcomp" then func = function(index) return BranchCompare(params[2], index) end
	elseif params[1] == "relbranchcomp" then func = function(index) return RelativeBranchCompare(params[2], index) end
	elseif params[1] == "diff" then func = function(index) return Difference(params[2], index) end
	elseif params[1] == "branchval" then func = function(index) return GetBranchLogValue(params[2], index) end
	elseif params[1] == "branchdiff" then func = function(index) return BranchDifference(params[2], index) end
	elseif params[1] == "oldval" then func = function(index) return GetOldLogValue(params[2], index) end
	elseif params[1] == "oldcomp" then func = function(index) return OldLogCompare(params[2], index) end
	elseif params[1] == "olddiff" then func = function(index) return OldLogDifference(params[2], index) end
	end
	
	if func == nil
	then return
	end
	
	if Logs[params[2]] == nil
	then console.log("WARNING: "..params[2].." is not defined as a log in "..expr)
		 return
	end
	
	if params[3] ~= nil
	then if Logs[params[3]] == nil
		 then console.log("WARNING: "..params[3].."is not defined as a log in "..expr)
		 return
		 end
		 return function(index) return Normalize(func(index), params[3], index) end
	else return func
	end
	
end

local function GetColorFromString(s)
	
	if type(s) ~= "string"
	then return
	end

	if string.match(s, "^#%x%x%x%x%x%x$") -- "#RRGGBB" RGB value
	then return tonumber("0xFF"..s)
	elseif string.match(s, "^#%x%x%x%x%x%x%x%x$") -- "#AARRGGBB" ARGB color value
	then return tonumber("0x"..s)
	elseif string.match(s, "^0x%x%x%x%x%x%x%x%x$") -- "0xAARRGGBB"
	then return tonumber(s)
	elseif string.match(s, "^Color%.%l+$") -- Colors table
	then return Colors[string.match(s, "^Color%.(%l+)$")]
	end

end

local function ConditionalExpressionLookup(expr, valexpr)

	local params = bizstring.split(expr, ",")
	local param = tonumber(params[2])
		
	if params[1] == "onchange" then return function(value, index) return PrintOnChange(valexpr, index) end
	elseif params[1] == "whennot" then return function(value, index) return value ~= (param or 0) and true or false end
	elseif params[1] == "when" then return function(value, index) return value == (param or 0) and true or false end
	elseif params[1] == "whenabove" then return function(value, index) return value > (param or 0) and true or false end
	elseif params[1] == "whenbelow" then return function(value, index) return value < (param or 0) and true or false end
	end

end

local function ColorExpressionLookup(expr, valexpr)

	local funcs = bizstring.split(expr, ";")
	local k
	local when -- to color
	local how -- to color
	
	when = ConditionalExpressionLookup(funcs[1], valexpr)

	if when == nil
	then k = 1 -- no when function, how function must be first in string
	else k = 2 -- when function exists at index 1, how function comes after it
	end
	
	if funcs[k] == nil
	then console.log("WARNING: No shorthandform for colorexpression function provided for "..expr..".")
		 return
	end
	
	if GetColorFromString(funcs[k])
	then local color = GetColorFromString(funcs[k])
		 how = function(value, index) return Color(index, color) end

	elseif bizstring.startswith(funcs[k], "gradient,") -- ColorGradient function shorthand form
	then local params = bizstring.split(funcs[k], ",") -- "gradient,<maxvalue>[,<startcolor>][,<endcolor>][,<minvalue>][,<exponent>]"
		 local startcolor = GetColorFromString(params[3])
		 local endcolor = GetColorFromString(params[4])
		 how = function(value, index) return ColorGradient(value, index, tonumber(params[2]), startcolor, endcolor, tonumber(params[5]), tonumber(params[6])) end
	
	elseif bizstring.startswith(funcs[k], "gradientabs,") -- ColorGradient function with absolute value shorthand form
	then local params = bizstring.split(funcs[k], ",") -- "gradientabs,<maxvalue>[,<startcolor>][,<endcolor>][,<minvalue>][,<exponent>]"
		 local startcolor = GetColorFromString(params[3])
		 local endcolor = GetColorFromString(params[4])
		 how = function(value, index) return ColorGradient(math.abs(value), index, tonumber(params[2]), startcolor, endcolor, tonumber(params[5]), tonumber(params[6])) end
		 
	elseif bizstring.startswith(funcs[k], "table,")
	then local colortable = _ENV[string.match(funcs[k], "^table,([%w_]+)$")] 
		 if colortable == nil
		 then console.log("WARNING: Table with the name "..tostring(string.match(funcs[k], "^table,([%w_]+)$")).." does not exist or is local.")
		 return
		 end
		 
		 how =  function(value, index) return MultiColor(value, index, colortable) end

	else console.log("WARNING: Third parameter colorexpression as a string must be \"#RRGGBB\" or \"#AARRGGBB\". Or shorthandform for gradient or table function.\nCurrently: "..expr)
		  return
	end
	
	if how == nil and when == nil
	then return
	end
	
	local defaultcolor
	
	if when ~= nil and funcs[k+1] ~= nil
	then defaultcolor = GetColorFromString(funcs[k+1])
	end
	
	return function(value, index) 
				
				if when ~= nil
				then if when(value, index) == true
					 then return how(value, index)
					 else return defaultcolor
					 end
				else return how(value, index)
				end
						  
		   end

end

-- Return the sign of the value x
local function sign(x)
	return x < 0 and -1 or x >= 0 and 1
end

local function signOrZero(x)
	return x < 0 and -1 or x > 0 and 1 or 0
end

local function TextExpressionLookup(expr, valexpr)

	local funcs = bizstring.split(expr, ";")
	local k
	local when -- to print
	local how -- to print
	
	when = ConditionalExpressionLookup(funcs[1], valexpr)
	
	if when == nil
	then k = 1 -- no 'when' function, 'how' function must be first in string
	else k = 2 -- 'when' function exists at index 1, 'how' function comes after it
	end
	
	local params = bizstring.split(funcs[k], ",")
	local param = tonumber(params[2])

	--TODO:index for how functions here not needed
	-- Display text as '<divisor>:<remainder>', optional parameter for divisor
	if params[1] == "modform" -- More understandable to prefix it with the sign and use the absolute values and not calculate the remainder as is
	then how = function(value, index) return tostring((sign(value) == -1 and "-" or "")..(math.abs(value)//(param or 256))..":"..math.abs(value)%(param or 256)) end
	
	-- Display text as '<divisor>:<remainder>' as hexadecimal, optional parameter for divisor
	elseif params[1] == "modformh"
	then how = function(value, index) return tostring(sign(value) == -1 and "-" or ""..string.format("%X:%X",math.abs(value)//(param or 256), math.abs(value)%(param or 256))) end
	
	-- Display text as '<divisor>:<remainder>' with divisor as decimal and remainder as hex, optional parameter for divisor
	elseif params[1] ==  "modformdh"
	then how = function(value, index) return tostring(sign(value) == -1 and "-" or ""..(math.abs(value)//(param or 256))..":"..string.format("%X", math.abs(value)%(param or 256))) end
	
	-- Display text as hexadecimal, optional parameter for number of digits
	elseif params[1] == "hex"
	then how = function(value, index) return string.format("%X", value):sub(param and -param or 0) end
	
	-- Display text from table
	elseif bizstring.startswith(funcs[k], "table,")
	then local texttable = _ENV[string.match(funcs[k], "^table,([%w_]+)$")] 
		 if texttable == nil
		 then console.log("WARNING: Table with the name "..tostring(string.match(funcs[k], "^table,([%w_]+)$")).." does not exist or is local.")
		 return
		 end
		 how = function(value, index) return texttable[value] or "" end
		 
	elseif funcs[k] and string.len(funcs[k]) > 0-- and how == nil and (when == nil or k==2)--(when==nil and how==nil or how==nil and k==2)
	then how = function(value, index) return tostring(funcs[k]) end -- Output the text, with or without a condition
	end
		
	if how == nil and when == nil
	then return -- Output nothing for empty strings
	end
	
	return function(value, index) 
				
				if when ~= nil 
				then if when(value, index) == true
					 then if how ~= nil 
						  then return how(value, index)
						  else return tostring(value)
						  end
					 else return ""
					 end
				elseif how ~= nil
				then return how(value, index)
				else return tostring(value)
				end 

		   end
	
end

-- Checks wheter the column belongs to the input button columns
local function IsButtonColumn(column)

	for button in pairs(joypad.getimmediate()) do
		if button == column
		then return true
		end
	end
	
	return false
	
end

--[[Adds a printer to the Printers table for outputing text and color into the TAStudio list.
	Sets predefined function depending on the parameters passed.
	Or the printer uses functions passed as parameters.
	
	Columnname is a string for TAStudio button list header.
	
	valueexpression needs to be a string or a function. 
	As a string it needs to be the name for a Logs table, or a shorthand function.
	As a function it needs to be a function that takes a number (index) as an argument and returns a number.
	
	colorexpression will define which colors will be drawn into the TAStudio list.
	It can be a a string with "#RRGGBB" or "#AARRGGBB" (A)RGB values, or a shorthandfunction.
	A number in the form of 0xAARRGGBB ARGB hexadecimal color value.
	A table with value/color pairs in which each value can be assigned a color.
	A function which takes two numbers (value, index) as an argument and returns a color.
	As nil or no parameter passed as a parameter for colorexpression, no color will be drawn.
	
	textexpression will define how text will be displayed in the TAStudio list.
	No text can be printed into button columns.
	When nil or no parameter passed the value for each frame will be displayed.
	It can be a string which will be displayed if the value for the corresponding frame is greater than zero. "" to display no text, or a shorthandfunction.
	A table with value/text pairs in which each value can be a assigned a text.
	False for which no text will be displayed.
	A function which takes a number (value) and a number (index) as an optional paramenter as arguments and returns a string.
	
	Digits is the number of digits to be displayed as text in TAStudio list, default is 3.]]
function AddPrinter(columnname, valueexpression, colorexpression, textexpression, digits)

	if Printers[columnname] ~= nil
	then console.log(columnname.." is already printed")
		 return
	end
	
	-- valueexpression parameter check
	if type(valueexpression) == "string" 
	then local expr = ValueExpressionLookup(valueexpression)
		 if expr ~= nil
		 then valueexpression = expr
		 else if Logs[valueexpression] == nil 
			  then console.log("WARNING: Invalid string valueexpression: "..tostring(valueexpression).."\n Must be name of a Log or shorthandform function.")
				   return
			  else local s = valueexpression
				   valueexpression = function(index) return Logs[s].values[index] end -- Value will be the value in the Log as is with the name of the string.
			  end
		 end
		 
	elseif type(valueexpression) ~= "function" -- Value will be the number returned from the function
		then console.log("WARNING: Second parameter valueexpression ("..tostring(valueexpression)..") must be a string or a function.")
			 return
	end
	
	-- colorexpression parameter check
	if type(colorexpression) == "table"
	then local colortable = colorexpression
		 colorexpression = function(value, index) return MultiColor(value, index, colortable) end
		 
	elseif type(colorexpression) == "number" -- 0xAARRGGBB ARGB value
	then local color = colorexpression
		 colorexpression = function(value, index) if value > 0 then return Color(index, color) end end
		 
	elseif type(colorexpression) == "string" 
	then local expr = ColorExpressionLookup(colorexpression, valueexpression)
		 if expr ~= nil
		 then colorexpression = expr
		 else colorexpression = nil
		 end
		 
	elseif type(colorexpression) ~= "nil" and type(colorexpression) ~= "function"
		then console.log("WARNING: Third parameter colorexpression must be a number, a string, a table, a function or nil.")	
			 return
	end
		
	-- textexpression parameter check
	if type(textexpression) == "nil"
	then textexpression = function(value) return tostring(value) end -- Display the value from the valueexpression as text
	elseif type(textexpression) == "string"
	then local expr = TextExpressionLookup(textexpression, valueexpression)
		 if expr ~= nil
		 then textexpression = expr
		 else textexpression = nil
		 end
		 
	elseif type(textexpression) == "table" -- Value, Text pairs for displaying text with the corresponding value
	then local t = textexpression
		 if next(t) ~= nil
		 then textexpression = function(value) return t[value] or "" end
		 else textexpression = nil
		 end
		 
	elseif type(textexpression) == "boolean"
	then if textexpression == true
		 then textexpression = function(value) return tostring(value) end -- Display the value from the valueexpression as text
		 else textexpression = nil -- Display no text 
		 end
		
	elseif type(textexpression) ~= "function"
	then console.log("WARNING: Fourth parameter textexpression must be a string, a table, a function, boolean or nil.")
	end
	------------------------------
	
	if IsButtonColumn(columnname)
	then if textexpression ~= nil
		 then console.log("WARNING: Can't print values into column "..columnname..".Only color will be displayed.")
		 end
		 
		 textexpression = nil -- Don't print text in button columns
		 
	else digits = digits or 3
		 tastudio.addcolumn(columnname, columnname, (digits * 6) + 14)
	end	
	
	Printers[columnname] = {value = valueexpression,
							color = colorexpression,
							text = textexpression}

end


------------
--	File  --
------------

-- Saves the log file to disk.
local function SaveFile(index, source)

	local filename = string.match(movie.filename(), "([%w%p%s]+)%.tasproj") 

	if index >= 0
	then filename = filename.."_"..tostring(index+1)..".log"--..tastudio.getbranches()[index].Id..".log"
	elseif index == -1
	then filename = filename.."_backup.log"
	elseif index == -2
	then filename = filename.."_current.log"
	elseif index == -3
	then filename = filename.."_old.log"
	else console.log("Invalid file save index")
		 return
	end
	
	if string.match(movie.filename(), "default.tasproj") == "default.tasproj"
	then console.log("WARNING: The movie has yet not been saved. The log file wasn't saved.")
		 return
	elseif loaderror == true
	then console.log("The file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)").." wasn't saved as a load error occured and the log files may contain data from another script.\nDelete or move the files from the current movie folder if you want to start a new log and restart the script")
		 return
	end

	local file = io.open(filename, "w+")
	
	source = source or Logs
	
	file:write("ungreenframe:\n"..tostring(ungreenframe).."\n")
	
	for k in pairs(source) do -- Save header with log order
		file:write(tostring(k).."|")
	end
	
	file:write("\n")
	
	for i = 0, movie.length(), 1 do -- Save log values to file
		for k, v in pairs(source) do
			file:write(tostring(source[k].values[i]).."|")
		end
		
		file:write("\n")
	end
	
	file:close()
	
	gui.addmessage("Saved log file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)"))

end

-- Loads the log file from disk
local function LoadFile(index, target)

	--local filename = string.match(movie.filename(), "[%w%p%s\\]+\\([%w%p%s]+)%.tasproj") 
	local filename = string.match(movie.filename(), "([%w%p%s]+)%.tasproj") 
	
	if index >= 0
	then filename = filename.."_"..tostring(index+1)..".log"--tastudio.getbranches()[index].Id..".log"
	elseif index == -1
	then filename = filename.."_backup.log"
	elseif index == -2
	then filename = filename.."_current.log"	
	elseif index == -3
	then filename = filename.."_old.log"
	else console.log("Error: Invalid file load index")
		 return
	end
	
	local file = io.open(filename, "r")
	
	if file ~= nil
	then file:read("l")
		 ungreenframe = tonumber(file:read("l"))
		 
		 local names = bizstring.split(file:read("l"), "|")
		 for k,v in pairs(names) do
			if Logs[v] == nil
			then file:close() -- Logs in the file don't match the defined logs in the script
				 console.log("Error reading file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)").."\n"..v.." is not defined as a log.\nYou may have opened the wrong movie or script.")
				 loaderror = true
				 return
			end
			
			if target[v] == nil
			then target[v] = {values = {}}
			end
		 end
		 
		 local i = 0
		 for line in file:lines("l") do -- Load log values from the file
			local s = bizstring.split(line, "|")
			
			for k,v in pairs(names) do	
				target[v].values[i] = tonumber(s[k])
			end
			
			i = i + 1
		 end
		 
		 file:close()
		 gui.addmessage("Loaded log file "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)"))
	--else console.log("File "..string.match(filename, "[%w%p%s\\]+\\([%w%p%s]+)").." was not found,")
	end
	
end


----------------
--	TAStudio  --
----------------

-- Responsible for printing text into the TAStudio list
local function TAStudioText(index, column)

	if Printers[column] ~= nil
	then if Printers[column].text ~= nil and Printers[column].value(index) ~= nil
		 then return Printers[column].text(Printers[column].value(index), index)
		 elseif IsButtonColumn(column) == false -- Don't make input invisible for button columns
			 then return ""
		 end
	end
	
end

-- Responsible for coloring cells in the TAStudio list
local function TAStudioColor(index, column)

	if Printers[column] ~= nil and Printers[column].color ~= nil and Printers[column].value(index) ~= nil
	then return Printers[column].color(Printers[column].value(index), index) or nil
	end
	
	return nil

end

-- Called when the greenzone in invalidated by editing the movie
local function Ungreen(index)
	
	-- This may not be the desired behaviour when considering the old log should only change on invalid greenzone
	-- Copy to oldLogs when the input was edited after it was edited on an earlier frame and the user player over that section
	-- if lasteditedframe and lasteditedframe < index and lasteditedframe < ungreenframe
	-- then for k,v in pairs(Logs) do
			-- for i = lasteditedframe, index-1, 1 do
				-- oldLogs[k].values[i] = Logs[k].values[i]  
			-- end
		 -- end
	-- end
	
	lasteditedframe = index
	
	if ungreenframe > index
	then ungreenframe = index-- - 1
	end

end


-------------
--	Color  --
-------------

Colors = {}
Colors.white =	0xFFFFFFFF
Colors.gray =	0xFFFFFFFF
Colors.red = 	0xFFFF4D4D
Colors.green =	0xFF4DFF4D
Colors.blue = 	0xFF4D4DFF
Colors.yellow = 0xFFFFFF4D
Colors.magenta =0xFFFF00FF
Colors.cyan =	0xFF4DFFFF
Colors.orange = 0xFFFFA64D
Colors.purple = 0xFFD047FF

local function CalcPaleColor(alpha, red, green, blue)

	red = math.floor((255-red)/120*(200-240)+255)
	green = math.floor((255-green)/120*(200-240)+255)
	blue = math.floor((255-blue)/120*(200-240)+255)
	
	return (alpha*0x1000000)+(red*0x10000)+(green*0x100)+blue

end

local function CalcPaleColor2(color)

	local alpha = (color & 0xFF000000)>>24
	local red = (color & 0x00FF0000)>>16
    local green = (color & 0x0000FF00)>>8
	local blue = (color & 0x000000FF)
	
	red = math.floor((255-red)/120*(200-240)+255)
	green = math.floor((255-green)/120*(200-240)+255)
	blue = math.floor((255-blue)/120*(200-240)+255)
	
	return (alpha*0x1000000)+(red*0x10000)+(green*0x100)+blue

end

function Color(index, color)
	
	if index < ungreenframe
	then return color
	else return CalcPaleColor2(color)
	end
	
end

-- Each value in the table can be assigned a color
function MultiColor(value, index, colortable)

	if colortable[value] ~= nil
	then if index < ungreenframe
		 then return colortable[value]
		 else return CalcPaleColor2(colortable[value])
		 end
	end
	
end

-- Returns the value or minimum if value < minimum or maximum if value > maximum
-- local function Clamp(value, minimum, maximum)
	-- return math.max(minimum, math.min(value, maximum))
-- end
-- Optimized version of above
local function Clamp(value, minimum, maximum)
	return value < maximum and value > minimum and value or value >= maximum and maximum or value <= minimum and minimum
end

-- The gradient starts at startcolor for values below minvalue and ends at endcolor for value greater than maxvalue.
-- When no paremeters for startcolor and endcolor are passed into the function, then the gradient will be from red to green.
-- The default value for minvalue is 0.
-- An optional parameter called exponent controls the distribution of the gradient.
function ColorGradient(value, index, maxvalue, startcolor, endcolor, minvalue, exponent)

	minvalue = minvalue or 0
	
	-- fraction is a value from 0 to 1, with minvalue being eqaul to 0 and maxvalue equal to 1
	local fraction = ( Clamp(value-minvalue, 0, maxvalue-minvalue)/(maxvalue - minvalue) )^(exponent or 1)
	
	startcolor = startcolor or 0xFFFF0000 -- default red
	local as = (startcolor & 0xFF000000)>>24
	local rs = (startcolor & 0x00FF0000)>>16
    local gs = (startcolor & 0x0000FF00)>>8
	local bs = (startcolor & 0x000000FF)
	
	endcolor = endcolor or 0xFF00FF00 -- default green
	local ae = (endcolor & 0xFF000000)>>24
	local re = (endcolor & 0x00FF0000)>>16
	local ge = (endcolor & 0x0000FF00)>>8
	local be = (endcolor & 0x000000FF)
	
	local alpha
	local red  
	local green
    local blue 
	
	if re - rs <= 32 and ge - gs <= 32 and be - bs <= 32 -- only descending slopes + tolerance
	or re - rs >= -32 and ge - gs >= -32 and be - bs >= -32 -- only ascending slopes + tolerance
	then -- Clamps the color value to the slope function y = (_e - _s)*x + _s
		 -- between _s and _e, where _s is the startcolor a,r,g,b part and _e is the endcolor part. 
		 alpha = math.floor(Clamp((ae-as)*fraction + as, math.min(as,ae), math.max(as, ae)))
		 red   = math.floor(Clamp((re-rs)*fraction + rs, math.min(rs,re), math.max(rs, re)))
		 green = math.floor(Clamp((ge-gs)*fraction + gs, math.min(gs,ge), math.max(gs, ge)))
		 blue  = math.floor(Clamp((be-bs)*fraction + bs, math.min(bs,be), math.max(bs, be)))
	else -- Clamps the color value to a slope function y = 2*(_e - _s)*x +_s for ascending slopes, 
		 -- or y = 2*(_e - _s)*x + 2*_s-_e for descending slopes between _s and _e.
		 -- This makes ascending slope functions occupy fraction value between 0 and 0.5
		 -- and descending ones between 0.5 and 1. Scaled on the x-axis by half and shifted by 0.5 for descending slopes.
		 alpha = math.floor(Clamp((ae-as)*2*fraction + ((ae-as)<0 and 2*as-ae or as), math.min(as,ae), math.max(as, ae)))
		 red   = math.floor(Clamp((re-rs)*2*fraction + ((re-rs)<0 and 2*rs-re or rs), math.min(rs,re), math.max(rs, re)))
		 green = math.floor(Clamp((ge-gs)*2*fraction + ((ge-gs)<0 and 2*gs-ge or gs), math.min(gs,ge), math.max(gs, ge)))
		 blue  = math.floor(Clamp((be-bs)*2*fraction + ((be-bs)<0 and 2*bs-be or bs), math.min(bs,be), math.max(bs, be)))
	end
	
	if index >= ungreenframe
	then return CalcPaleColor(alpha, red, green, blue)
	end
	
	return (alpha*0x1000000)+(red*0x10000)+(green*0x100)+blue

end


---------------
--	Utility  --
---------------

-- Checks wheter the value is contained in the values table
-- UNUSED
-- function AnyValueMatching(value, values)

	-- for k,v in pairs(values) do
		-- if value == v
		-- then return true
		-- end
	-- end
	
	-- return false
-- end

local function BranchCount()

	if tastudio.getbranches()[0] == nil
	then return 0
	else return #tastudio.getbranches() + 1
	end
	
end

-- Calculates the difference between current log value with the corresponding name and the value in the branch log for the selected branch
-- function BranchCompare(name, index)

	-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
	-- then return Logs[name].values[index] - branches[selectedbranch][name].values[index]
	-- end
	
-- end
function BranchCompare(name, index)
	
	local branch = branches[selectedbranch]
	
	if branch -- Need to check if log for branch exists, the user may load a .tasproj containing branches without logs
	then local branchvalue = branch[name].values[index]
		 local logvalue = Logs[name].values[index]
		 
		 return logvalue and branchvalue and logvalue - branchvalue
	end

end

-- UNUSED/OUTDATED:Similiar to BranchCompare, but multiplies the result by the sign of the value in the norm log
-- function BranchCompareNormalized(name, norm, index)

	-- -- Assume when Logs[name].value[index] not nil then Logs[norm].value[index] must also not be nil
	-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
	-- then return (Logs[name].values[index] - branches[selectedbranch][name].values[index])*sign(Logs[norm].values[index])
	-- end

-- end

-- Same as BranchCompare, but adjust for the difference for the current marker and uses an offset for the branch values index
-- function RelativeBranchCompare(name, index)

	-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index-markerdiff] ~= nil
	-- then return Logs[name].values[index] - branches[selectedbranch][name].values[index-markerdiff]
	-- end
	
-- end
function RelativeBranchCompare(name, index)

	local branch = branches[selectedbranch]
	
	if branch -- Need to check if log for branch exists, the user may load a .tasproj containing branches without logs
	then local branchvalue = branch[name].values[index-markerdiff]
		 local logvalue = Logs[name].values[index]
		 
		 return logvalue and branchvalue and logvalue - branchvalue
	end
	
end

-- UNUSED/OUTDATED:
-- function RelativeBranchCompareNormalized(name, norm, index)

	-- -- Assume when Logs[name].value[index] not nil then Logs[norm].value[index] must also not be nil
	-- if Logs[name].values[index] ~= nil and branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index-markerdiff] ~= nil
	-- then return (Logs[name].values[index] - branches[selectedbranch][name].values[index-markerdiff])*sign(Logs[norm].values[index])
	-- end

-- end

-- Difference from previous frame
-- function Difference(name, index)

	-- if Logs[name].values[index] ~= nil and Logs[name].values[index-1] ~= nil
	-- then return Logs[name].values[index] - Logs[name].values[index-1]
	-- end

-- end
function Difference(name, index)

	local logvalue1 = Logs[name].values[index]
	local logvalue2 = Logs[name].values[index-1]

	return logvalue1 and logvalue2 and logvalue1 - logvalue2

end

-- Multiplies 'value' by the sign of 'norm'
-- function Normalize(value, norm, index)

	-- if value ~= nil
	-- then return value * sign(Logs[norm].values[index])
	-- end

-- end
function Normalize(value, norm, index)

	return value and value * sign(Logs[norm].values[index])

end

-- function GetLogValue(name, index)

	-- if Logs[name].values[index] ~= nil
	-- then return Logs[name].values[index]
	-- end 
	
-- end
function GetLogValue(name, index)

	return Logs[name].values[index]
	
end

-- function GetBranchLogValue(name, index)

	-- if branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
	-- then return branches[selectedbranch][name].values[index]
	-- end

-- end
function GetBranchLogValue(name, index)

	local branch = branches[selectedbranch]
	return branch and branch[name].values[index]

end

-- Difference from previous frame for branch log values
-- function BranchDifference(name, index)

	-- if branches[selectedbranch] ~= nil and branches[selectedbranch][name] ~= nil and branches[selectedbranch][name].values[index] ~= nil
	   -- and branches[selectedbranch][name].values[index-1] ~= nil
	-- then return branches[selectedbranch][name].values[index] - branches[selectedbranch][name].values[index-1]
	-- end

-- end
function BranchDifference(name, index)

	local branch = branches[selectedbranch]
	
	if branch
	then local branchvalue1 = branch[name].values[index]
		 local branchvalue2 = branch[name].values[index-1]
		 
		 return branchvalue1 and branchvalue2 and branchvalue1 - branchvalue2
	end

end

-- function GetOldLogsValue(name, index)

	-- if oldLogs[name] ~= nil and oldLogs[name].values[index] ~= nil
	-- then return oldLogs[name].values[index]
	-- end

-- end
function GetOldLogValue(name, index)

	return oldLogs[name].values[index]

end

-- function OldLogsCompare(name, index)

	-- if Logs[name].values[index] ~= nil and oldLogs[name] ~= nil and oldLogs[name].values[index] ~= nil
	-- then return Logs[name].values[index] - oldLogs[name].values[index]
	-- end

-- end
function OldLogCompare(name, index)

	local logvalue = Logs[name].values[index]
	local oldvalue = oldLogs[name].values[index]
	
	return logvalue and oldvalue and logvalue - oldvalue

end

function OldLogDifference(name, index)

	local oldvalue1 = oldLogs[name].values[index]
	local oldvalue2 = oldLogs[name].values[index-1]

	return oldvalue1 and oldvalue2 and oldvalue1 - oldvalue2

end

-- With this function, text or colors can be drawn into TAStudio when the value has changed from the previous one.
-- This function doesn't use 'name' as a parameter since it should decide wheter the value from 'valueexpression' has changed and not the values from a Log.
-- function PrintOnChange(valueexpression, index)

	-- if valueexpression(index) ~= nil and valueexpression(index-1) ~= nil
	-- then return (valueexpression(index) - valueexpression(index-1)) ~= 0 and true or false 
	-- end
	
	-- return true

-- end
function PrintOnChange(valueexpression, index)

	local value1 = valueexpression(index)
	local value2 = valueexpression(index-1)

	return value1 and value2 and value1 ~= value2 and true or false

end


-----------------
-- Status Bar ---
-----------------

-- Subtracts the frame number for the marker in the selected branch with the same note as the marker passed as the parameter from the parameter marker frame
local function CaclulateDifferenceToBranchMarker(markerframe)

	if BranchCount() == 0
	then return
	end

	local text = tastudio.getmarker(markerframe)
	
	for k, v in pairs(tastudio.get_frames_with_markers(tastudio.getbranches()[selectedbranch-1].Id)) do
		if text == tastudio.getmarker(v, tastudio.getbranches()[selectedbranch-1].Id)
		then markerdiff =  markerframe - v
			 break
		end
	end
	
end

-- Finds the first marker begining from start frame upwards that hasn't an empty note or doesn't start with "-"
local function FindSectionStart(start)

	local current = tastudio.find_marker_on_or_before(start)
	
	while bizstring.startswith(tastudio.getmarker(current), "-") or tastudio.getmarker(current) == "" do
		current = tastudio.find_marker_on_or_before(current-1)
	end
	
	return current
	
end

-- Finds the first marker beginning from start frame downwards that hasn't an empty note or doesn't start with "-"
local function FindSectionEnd(start, branchId)

	local marker = tastudio.find_marker_on_or_before(start, branchId)
	local markers = tastudio.get_frames_with_markers(branchId)
	
	for k,v in pairs(markers) do
	
		if v == marker -- Find marker from start frame
		then repeat k = k + 1 -- Search the list markers downwards until it finds an marker with a note that doesn't start with "-" or isn't empty
				if k > #markers
				then return nil
				end
			 until not (bizstring.startswith(tastudio.getmarker(markers[k], branchId), "-") or tastudio.getmarker(markers[k], branchId) == "")

			 return markers[k]
		end
	end

end

local function OpenDropdown()
	
	if BranchCount() == 0
	then return
	end

	if open
	then open = false
	else open = true
	end

end

local function SelectPreviousBranch()

	if BranchCount() == 0
	then return
	end
	
	if selectedbranch > 1
	then selectedbranch = selectedbranch - 1
	end
	
	CaclulateDifferenceToBranchMarker(currentsection)

end

local function SelectNextBranch()

	if BranchCount() == 0
	then return
	end

	if selectedbranch < BranchCount() 
	then selectedbranch = selectedbranch + 1 
	end
	
	CaclulateDifferenceToBranchMarker(currentsection)

end

-- Finds the section end marker after the current section start
-- Tries to find those two markers in branches
-- Selects the branch with the shortest time between those two markers in that branch
-- If no current section end marker, it finds the section end marker in branches
-- If no section end marker found in branches either, it uses the branch with earliest section start marker
local function SelectBestBranch() 

	if BranchCount() == 0
	then return
	end
	
	local startmarker = currentsection
	local endmarker = FindSectionEnd(startmarker)

	local best = 99999999
	local earliest = 99999999
	local index = nil -- With branchstartmarker and branchendmarker not nil
	local index2 = nil -- Only branchstartmarker not nil
	
	for k, branch in pairs(tastudio.getbranches()) do
		
		local branchmarkers = tastudio.get_frames_with_markers(branch.Id)
		
		local branchstartmarker = nil
		local branchendmarker = nil
		
		for _, markerframe in pairs(branchmarkers) do
		
			if tastudio.getmarker(startmarker) == tastudio.getmarker(markerframe, branch.Id)
			then branchstartmarker = markerframe
				 if endmarker == nil
				 then branchendmarker = FindSectionEnd(branchstartmarker, branch.Id)
				 end
			elseif endmarker ~= nil and tastudio.getmarker(endmarker) == tastudio.getmarker(markerframe, branch.Id)
				then branchendmarker = markerframe
			end
			
			if branchstartmarker ~= nil and branchendmarker ~= nil or branchstartmarker ~= nil and endmarker == nil
			then break
			end
			
		end
		
		if branchstartmarker ~= nil and branchendmarker ~= nil and branchendmarker - branchstartmarker < best
		then best = branchendmarker - branchstartmarker
			 index = k
		elseif branchstartmarker ~= nil and branchendmarker == nil and branchstartmarker < earliest
			then earliest = branchstartmarker
				 index2 = k
		end
	
	end

	if endmarker == nil and index == nil and index2 ~= nil -- No section end marker found in branches
	then selectedbranch = index2 + 1
	elseif index ~= nil
		then selectedbranch = index + 1
	end 
	
	CaclulateDifferenceToBranchMarker(currentsection)
	
end

local clickitems = {{x1=200, y1=18, x2=216, y2=2, clickFunction = OpenDropdown, text = "▲"},
					{x1=220, y1=18, x2=236, y2=2, clickFunction = SelectPreviousBranch, text = "-"},
					{x1=240, y1=18, x2=256, y2=2, clickFunction = SelectNextBranch, text = "+"},
					{x1=260, y1=18, x2=302, y2=2, clickFunction = SelectBestBranch, text = "Best", xt=260, yt=18, size=16}}

local function DrawDropdown(mousex, mousey)

	local text = tastudio.getmarker(currentsection)

	for k, branch in pairs(tastudio.getbranches()) do
	
		if k == selectedbranch - 1
		then gui.drawBox(60, client.screenheight()-18-16*(k+1), 200, client.screenheight()-2-16*(k+1), 0xFFFFFFFF, 0xFF606060, "client")
		else gui.drawBox(60, client.screenheight()-18-16*(k+1), 200, client.screenheight()-2-16*(k+1), 0xFFFFFFFF, 0xFF202020, "client")
		end
		
		if mousex > 60 and mousex < 200 and mousey > client.screenheight()-18-16*(k+1) and mousey < client.screenheight()-2-16*(k+1)
		then gui.drawBox(60, client.screenheight()-18-16*(k+1), 200, client.screenheight()-2-16*(k+1), 0xFFFFFFFF, 0xFF808080, "client")
			 if input.getmouse()["Left"] and not mousedown
			 then open = false
				  if k ~= selectedbranch - 1
				  then selectedbranch = k+1
					   CaclulateDifferenceToBranchMarker(currentsection)
				  end
			 end
		end
		
		local diff = ""
		
		for _, frame in pairs(tastudio.get_frames_with_markers(tastudio.getbranches()[k].Id)) do
			if text == tastudio.getmarker(frame, tastudio.getbranches()[k].Id)
			then diff =  tostring(frame - currentsection)
				break
			end
		end

		local branchtext = "#"..(k+1)..": "..(branch.Text or "").." "..diff
				
		if #branchtext >= 17 
		then branchtext = bizstring.remove(branchtext, 17-#diff-1, #branchtext-17)
		end
		
		gui.drawText(65, client.screenheight()-18-16*(k+1), branchtext, nil, nil, nil, nil, nil, nil, nil, "client")
		
	end	

end

local function DrawClickItems(mousex, mousey)

	for k,v in pairs(clickitems) do
	
		gui.drawBox(v.x1, client.screenheight()-v.y1, v.x2, client.screenheight()-v.y2, 0xFFFFFFFF, 0xFF202020, "client")

		if mousex > v.x1 and mousex < v.x2 and mousey > client.screenheight()-v.y1 and mousey < client.screenheight()-v.y2
		then gui.drawBox(v.x1, client.screenheight()-v.y1, v.x2, client.screenheight()-v.y2, 0xFFFFFFFF, 0xFF808080, "client")
			 
			 if input.getmouse()["Left"] and not mousedown
			 then v.clickFunction()
			 end
		end
		
		gui.drawText(v.xt or v.x1-3, v.yt and client.screenheight()-v.yt or client.screenheight()-v.y1-4, v.text, nil, nil, v.size or 24, nil, "bold", nil, nil, "client")
				
	end

end

local function StatusBar()


	gui.drawText(2, client.screenheight()-18, "Branch:", nil, nil, nil, nil, nil, nil, nil, "client")
	gui.drawBox(60, client.screenheight()-18, 200, client.screenheight()-2, 0xFFFFFFFF, 0xFF202020, "client")
		
	if BranchCount() > 0 and tastudio.getbranches()[selectedbranch-1]
	then local branchtext = "#"..selectedbranch..": "..(tastudio.getbranches()[selectedbranch-1].Text or "")
	
		 if #branchtext >= 17
		 then branchtext = bizstring.remove(branchtext, 17, #branchtext-17)
		 end
		 
		 gui.drawText(65, client.screenheight()-18, branchtext, nil, nil, nil, nil, nil, nil, nil, "client")
	end
	
	gui.drawText(308, client.screenheight()-18, "Section: "..tostring(tastudio.getmarker(currentsection)).." "..tostring(markerdiff), nil, nil, nil, nil, nil, nil, nil, "client")
	
	local mousex = input.getmouse()["X"]*(client.screenwidth()) / (client.bufferwidth()) 
	local mousey = input.getmouse()["Y"]*(client.screenheight()-20) / (client.bufferheight())
	
	DrawClickItems(mousex, mousey)
	
	if open == true
	then DrawDropdown(mousex, mousey)
	end
	
end


----------------
--	Branches  --
----------------

local function deepcopy(orig)

    local orig_type = type(orig)
    local copy
	
    if orig_type == 'table' then
        copy = {}
        for orig_key, orig_value in next, orig, nil do
            copy[deepcopy(orig_key)] = deepcopy(orig_value)
        end
        setmetatable(copy, deepcopy(getmetatable(orig)))
    else copy = orig -- number, string, boolean, etc
    end
	
    return copy
	
end

-- Called when a branch is loaded
local function BranchLoad(index)
	
	if index >= 0 
	then branches[-1] = {} -- backup for undo branch load
		 for k,v in pairs(Logs) do 
			local vals = deepcopy(Logs[k].values)
			branches[-1][k] = {values = {}}
			branches[-1].values = vals
		 end
	end

	-- TODO:Check ungreenframe and copy from current log if those are valid for branch log
	for k,v in pairs(Logs) do
	
		local vals = deepcopy(Logs[k].values)
		oldLogs[k].values = vals
	
		if branches[index+1] and branches[index+1][k] ~= nil
		then local vals = deepcopy(branches[index+1][k].values)
			 Logs[k].values = vals
		else Logs[k].values = {} -- Reset current log values in case there is no log for loaded branch
		end

	end
	
	currentsection = FindSectionStart(emu.framecount())
	CaclulateDifferenceToBranchMarker(currentsection)
	
end

-- Called when a branch is saved
local function BranchSave(index)
	
	if index >= 0
	then branches[index+1] = {}
		 for k,v in pairs(Logs) do
			local vals = deepcopy(Logs[k].values)
			branches[index+1][k] = { values = {} }
			branches[index+1][k].values = vals
		 end
		 
	end
	
	SaveFile(index)
	
end

-- Called when a branch is removed
local function BranchRemove(index)
	--TODO: use branch id, not possible in 2.11, onbranchremove called after the branch is deleted, it will get id of next branch
	
	if selectedbranch >= BranchCount()
	then selectedbranch = 1
	end

	branches[-1] = {}
	for k,v in pairs(Logs) do -- make backup
		local vals = deepcopy(Logs[k].values)
		branches[-1][k] = {values = {}}
		branches[-1][k].values = vals
	end
		
	if branches[index+1] 	
	then table.remove(branches, index+1)
	end
	
	local filename = string.match(movie.filename(), "([%w%p%s]+)%.tasproj") 
	
	--os.remove(filename.."_"..tostring(index+1).."_deleted.log") -- delete the backup
	os.remove(filename.."_deleted.log") -- delete the backup
	os.rename(filename.."_"..tostring(index+1)..".log", filename.."_deleted.log") -- rename for backup
	
	for i = index+1, BranchCount(), 1 do
		os.rename(filename.."_"..tostring(i+1)..".log", filename.."_"..tostring(i)..".log")
	end

end


----------
-- Main --
----------

-- Called after each frame
local function LogValues()

	for k, v in  pairs(Logs) do
		if emu.framecount() >= ungreenframe -- only change the old log for ungreenzoned frames
		then oldLogs[k].values[emu.framecount()] = Logs[k].values[emu.framecount()] 
		end
		Logs[k].values[emu.framecount()] = Logs[k].expression() -- Log values
	end

end

local function FrameEnd()
	
	if emu.framecount() > ungreenframe
	then ungreenframe = emu.framecount()
	end
	
	LogValues()
	
end

-- Called when the script is closed
local function Exit()

	-- if not loaderror -- Don't save the Log to current Log, if there was an error during loading files
	-- then 
	-- end
	
	SaveFile(-2)
	SaveFile(-3, oldLogs)
	-- Empty tables for script reloading
	Logs = {}
	Printers = {}
	branches = {}
	oldLogs= {}

end

function UpdateLogger()

	if currentmarker ~= lastmarker
	then currentsection = FindSectionStart(emu.framecount())
		 CaclulateDifferenceToBranchMarker(currentsection)
	end
	
	lastmarker = currentmarker
	currentmarker = tastudio.find_marker_on_or_before(emu.framecount())
	
	StatusBar()
	mousedown = input.getmouse()["Left"]
	
end

function InitializeLogger()

	if tastudio.engaged() == false
	then console.log("WARNING: This script only works with TAStudio open.")
	else selectedbranch = 1
		 ungreenframe = 0

		 currentmarker = tastudio.find_marker_on_or_before(emu.framecount())
		 currentsection = FindSectionStart(emu.framecount())

		 for i = 0, BranchCount()-1, 1 do
			local logs_ = {}
			LoadFile(i, logs_)
			
			if logs_[next(Logs)] -- Checks if the first key of Logs table is also a key of logs_ table. Doing the check here saves nil checks for Log value functions
			then branches[i+1] = logs_
			end
		 end

		 LoadFile(-3, oldLogs)
		 LoadFile(-2, Logs) -- Load files saved when script is stopped
	 
		 tastudio.onqueryitemtext(TAStudioText)
		 tastudio.onqueryitembg(TAStudioColor)
		 tastudio.ongreenzoneinvalidated(Ungreen)
		 
		 tastudio.onbranchsave(BranchSave)
		 tastudio.onbranchload(BranchLoad)
		 tastudio.onbranchremove(BranchRemove)

		 event.onframeend(FrameEnd)
		 event.onexit(Exit)
		 
		 client.SetClientExtraPadding(0,0,0,20)
	end
	
end