Module:Infobox regtemp

From Xenharmonic Reference
Revision as of 01:04, 1 March 2026 by Inthar (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:Infobox regtemp/doc

local p = {}

local infobox = require("Module:Infobox")
local u = require("Module:Utils")
local yesno = require("Module:Yesno")

function p.infobox_RT(frame)
	
	local name = frame.args["tempname"]
	
	local subgroup = frame.args["subgroup"]
	local basis = frame.args["commas"]
	
	local et1 = frame.args["edo_first"]
	local et2 = frame.args["edo_second"]
	local et3 = frame.args["edo_third"]
	local et4 = frame.args["edo_fourth"]
	
	local tuning = frame.args["tuning"]
	local genfrac = frame.args["genfrac"]
	local method = frame.args["method"]
	local mapping = frame.args["mapping"]
	
	local mos_override = frame.args["mosses"]
	local ploidacot_override = frame.args["ploidacot"]
	local colorname = frame.args["colorname"]
	local pergen = frame.args["pergen"]
	
	local limit1 = frame.args["lim1"]
	local comp1 = frame.args["comp1"]
	local acc1 = frame.args["acc1"]
	local limit2 = frame.args["lim2"]
	local comp2 = frame.args["comp2"]
	local acc2 = frame.args["acc2"]
	local debugg = frame.args["debug"]
	
	local data = {}
	
	-- process mapping
	local rank = p.count_matches(mapping, ";") + 1
	local period = string.match(mapping, "(-?%d+);")
	local genchain_mapping = {}
	for str in mapping:gmatch("(-?%d+)") do
		table.insert(genchain_mapping, str)
	end
	
	-- interpret subgroup
	local subgroup_basis = {}
	for str in subgroup:gmatch("(%d+/?%d*)") do
		table.insert(subgroup_basis, str)
	end
	
	local equave = u.eval_num_arg(subgroup_basis[1])
	
	-- process et join
	function process_et_join(et)
		local et_eq, et_num = p.warts2ed(et)
		local et_eq_letter = p.equave2letter (et_eq)
		return "[[" .. et_num .. "ed" .. et_eq_letter .. "|" .. et .. "]]"
	end
	
	local et_table = {}
	for _, et in ipairs({et1, et2, et3, et4}) do
		if u.value_provided(et) then
			table.insert(et_table, process_et_join (et))
		end
	end
	local et_join = table.concat(et_table, " & ")
	
	-- process generators
	function process_generators(genfrac_item, tuning_item)
		return "~" .. genfrac_item .. " = " .. tuning_item .. "{{c}}"
	end
	
	local genfrac_table = {}
	for str in genfrac:gmatch("(%d+/?%d*)") do
		table.insert(genfrac_table, str)
	end
	local tuning_table = {}
	for str in tuning:gmatch("(-?%d+%.?%d*)") do
		table.insert(tuning_table, str)
	end
	local generators_table = {}
	for i = 1, math.min(#genfrac_table, #tuning_table) do
		table.insert(generators_table, 
			process_generators (genfrac_table[i], tuning_table[i]))
	end
	local generators = table.concat (generators_table, ", ")
	
	-- default value for mos
	local mos = "n/a"
	
	-- autocalculating ploidacot
	if rank == 2 then
		local ploid = tonumber(period)
		local referent = u.eval_num_arg(subgroup_basis[2])
		local cot = tonumber(genchain_mapping[2])
		local suffix = "cleft"
		if equave == 2 then -- octave-based temp
			if referent == 3 then
				suffix = "cot"
			elseif referent == 5 then
				suffix = "seph"
			end
		elseif equave == 3 then -- twelfth-based temp
			if referent == 5 then
				referent_candidate = u.eval_num_arg(subgroup_basis[3])
				if referent_candidate == 7 then
					referent = referent_candidate
					cot = tonumber(genchain_mapping[3])
					suffix = "gem"
				end
			end
		end
		
		if suffix == "cleft" then
			suffix = referent .. suffix
		end
		
		local equave_size = 1200 * u.log2(equave)
		local referent_size = 1200 * u.log2(referent)
		local period_size = equave_size / ploid
		local generator_size = tonumber(tuning_table[1])
		if cot < 0 then
			generator_size = equave_size - generator_size
			cot = -cot
		end
		
		-- find the shear
		local shear
		if cot ~= 0 then
			shear = (math.floor(generator_size * cot / period_size) 
				- math.floor(referent_size % equave_size / period_size)) % cot
		else
			shear = 0
		end
		
		-- omega extension
		if ploid == 1 and cot > 2 and shear == cot - ploid then
			shear = shear - cot
		end
		
		-- construct the ploidacot signature
		local MAX_GREEK_NUMERAL = 12 -- max number to convert to greek letters/numerals
		
		local ploid_sig
		if ploid == 1 then
			ploid_sig = "" -- omit the ploid part
		elseif ploid <= MAX_GREEK_NUMERAL then
			ploid_sig = p.num2greek(tostring(ploid), "ploid") .. "ploid "
		else
			ploid_sig = tostring (ploid) .. "-ploid "
		end
		
		local shear_sig
		if shear <= MAX_GREEK_NUMERAL then
			shear_sig = p.num2greeklet(tostring(shear))
		else
			shear_sig = tostring(shear) .. "-sheared "
		end
		
		local cot_sig
		if cot <= MAX_GREEK_NUMERAL then
			cot_sig = p.num2greek(tostring(cot), "cot") .. suffix
		else
			cot_sig = tostring(cot) .. "-" .. suffix
		end
		
		ploidacot = ploid_sig .. shear_sig .. cot_sig
	else
		ploidacot = "n/a"
	end
		
	-- user override
	if u.value_provided (mos_override) then
		mos = mos_override
	end
	if u.value_provided (ploidacot_override) then
		ploidacot = ploidacot_override
	end
	
	-- construct the table
	table.insert(data, {
		"Subgroups",
		subgroup
	})

	table.insert(data, {
		"Reduced mapping",
		"⟨" .. mapping .. "]"
	})

	table.insert(data, {
		"ET join",
		et_join
	})

	table.insert(data, {
		"Generators ([[" .. method .. "]])",
		generators
	})

	table.insert(data, {
		"MOS scales",
		mos
	})

	table.insert(data, {
		"Ploidacot",
		ploidacot
	})

	table.insert(data, {
		"Comma basis",
		basis
	})
	
	if u.value_provided (pergen) then
		table.insert(data, {
			"Pergen",
			pergen
		})
	end

	if u.value_provided (colorname) then
		table.insert(data, {
			"Color name",
			colorname
		})
	end
	
	-- error and stuff
	local limit_text
	if equave == 2 then
		limit_text = "-odd-limit"
	elseif equave == 3 then
		limit_text = "-throdd-limit"
	else
		limit_text = "-integer-limit"
	end
	local string1 = limit1 .. limit_text .. ": " .. acc1 .. "{{c}}"
	local string2 = limit1 .. limit_text .. ": " .. comp1 .. " notes"
	if u.value_provided (limit2) then
		string1 = string1 .. "; <br>" .. limit2 .. limit_text .. ": " .. acc2 .. "{{c}}"
		string2 = string2 .. "; <br>" .. limit2 .. limit_text .. ": " .. comp2 .. " notes"
	end

	table.insert(data, {
		"Minimax error",
		string1
	})

	table.insert(data, {
		"Target scale size",
		string2
	})

	local result = infobox.build(
		name,
		data
	)
	
	return frame:preprocess(debugg == true and "<pre>" .. result .. "</pre>" or result)
end

function p.count_matches(base, pattern)
    return select(2, string.gsub(base, pattern, ""))
end

function p.digit2greek(number, purpose)

	local greek
	
	if number == "1" then
		if purpose == "ploid" then
			greek = "ha"
		elseif purpose == "cot" then
			greek = "mono"
		elseif purpose == "decade" then
			greek = "deca"
		else
			greek = "hen"
		end
	elseif number == "2" then
		if purpose == "decade" then
			greek = "icosa"
		elseif purpose == "unit" then
			greek = "do"
		else
			greek = "di"
		end
	elseif number == "0" then
		if purpose == "unit" then
			greek = ""
		else
			greek = "a"
		end
	else
		if number == "3" then
			if purpose == "decade" then
				greek = "tria"
			else
				greek = "tri"
			end
		elseif number == "4" then
			if purpose == "decade" then
				greek = "tessera"
			else
				greek = "tetra"
			end
		elseif number == "5" then
			if purpose == "decade" then
				greek = "pente"
			else
				greek = "penta"
			end
		elseif number == "6" then
			if purpose == "decade" then
				greek = "hexe"
			else
				greek = "hexa"
			end
		elseif number == "7" then
			if purpose == "decade" then
				greek = "hebdome"
			else
				greek = "hepta"
			end
		elseif number == "8" then
			if purpose == "decade" then
				greek = "ogdoe"
			elseif purpose == "unit" then
				greek = "octo"
			else
				greek = "octa"
			end
		elseif number == "9" then
			if purpose == "decade" then
				greek = "enene"
			else
				greek = "ennea"
			end
		end
		if purpose == "decade" then
			greek = greek .. "conta"
		end
	end

	return greek
end

function p.num2greek(number, purpose)

	local greek
	
	if string.len(number) == 1 then
		greek = p.digit2greek(number, purpose)
	elseif string.len(number) == 2 then
		local unit = string.char(string.byte(number, 2))
		local decade = string.char(string.byte(number, 1))
		
		greek = p.digit2greek(unit, "unit") .. p.digit2greek(decade, "decade")
	end
	return greek
end

function p.digit2greeklet(number, purpose)

	local greek
	
	if number == "1" then
		if purpose == "decade" then
			greek = "iota-"
		elseif purpose == "unit" then
			greek = "alpha-"
		end
	elseif number == "2" then
		if purpose == "decade" then
			greek = "kappa-"
		elseif purpose == "unit" then
			greek = "beta-"
		end
	elseif number == "3" then
		if purpose == "decade" then
			greek = "lambda-"
		elseif purpose == "unit" then
			greek = "gamma-"
		end
	elseif number == "4" then
		if purpose == "decade" then
			greek = "mu-"
		elseif purpose == "unit" then
			greek = "delta-"
		end
	elseif number == "5" then
		if purpose == "decade" then
			greek = "nu-"
		elseif purpose == "unit" then
			greek = "epsilon-"
		end
	elseif number == "6" then
		if purpose == "decade" then
			greek = "xi-"
		elseif purpose == "unit" then
			greek = "wau-"
		end
	elseif number == "7" then
		if purpose == "decade" then
			greek = "omicron-"
		elseif purpose == "unit" then
			greek = "zeta-"
		end
	elseif number == "8" then
		if purpose == "decade" then
			greek = "pi-"
		elseif purpose == "unit" then
			greek = "eta-"
		end
	elseif number == "9" then
		if purpose == "decade" then
			greek = "qoppa-"
		elseif purpose == "unit" then
			greek = "theta-"
		end	
	elseif number == "0" then
		greek = ""
	elseif number == "-1" then
		greek = "omega-"
	end

	return greek
end

function p.num2greeklet(number)

	local greek
	
	if string.len(string.match(number, "(%d+)")) == 1 then
		greek = p.digit2greeklet(number, "unit")
	elseif string.len(string.match(number, "(%d+)")) == 2 then
		local unit = string.char(string.byte(number, 2))
		local decade = string.char(string.byte(number, 1))
		
		greek = p.digit2greeklet(decade, "decade") .. p.digit2greeklet(unit, "unit")
	end
	return greek
end

function p.equave2letter(et_eq)
	-- converts an equave string to a letter
	local et_eq_letter
	if et_eq == "2" then
		et_eq_letter = "o"
	elseif et_eq == "3" then
		et_eq_letter = "t"
	elseif et_eq == "3/2" then
		et_eq_letter = "f"
	else
		et_eq_letter = eq
	end
	return et_eq_letter
end

function p.warts2ed(warts)
	-- converts a wart notation to an equal division
	local wart_prefix, et_num = warts:match ("(%a?)(%d+)")
	local et_eq = "2"
	if wart_prefix == "a" then
		et_eq = "2"
	elseif wart_prefix == "b" then
		et_eq = "3"
	elseif wart_prefix == "c" then
		et_eq = "5"
	elseif wart_prefix == "d" then
		et_eq = "7"
	elseif wart_prefix == "e" then
		et_eq = "11"
	elseif wart_prefix == "f" then
		et_eq = "13"
	elseif wart_prefix == "g" then
		et_eq = "17"
	elseif wart_prefix == "h" then
		et_eq = "19"
	elseif wart_prefix == "i" then
		et_eq = "23"
	end
	return et_eq, et_num
end

return p