Module:ET

From Xenharmonic Wiki
Jump to navigation Jump to search

Todo: documentation


Documentation transcluded from /doc
local rat = require("Module:Rational")
local seq = require("Module:Sequence")
local p = {}

-- TODO: we should not represent the equave as a rational number at all

local common_suffix = {
	["3/2"] = "f",
	["2"] = "o",
	["2/1"] = "o",
	["3"] = "t",
	["3/1"] = "t",

	-- TODO: these should not be here
	["987/610"] = "ϕ",
	["1264/465"] = "n",
	["355/113"] = "π",
}

local common_ratio = {
	["f"] = rat.new(3, 2),
	["o"] = 2,
	["t"] = 3,

	-- TODO: these should not be here
	["ϕ"] = rat.new(987, 610),
	["n"] = rat.new(1264, 465),
	["π"] = rat.new(355, 113),
}

-- create a ET structure <size>ed<equave>
function p.new(size, equave, suffix)
	size = size or 12
	equave = equave or 2
	if suffix == nil then
		local equave_n, equave_m = rat.as_pair(equave)
		local equave_ratio = rat.as_ratio(equave)
		equave_ratio = equave_ratio:lower()
		suffix = "ed"
		if common_suffix[equave_ratio] then
			suffix = suffix .. common_suffix[equave_ratio]
		elseif equave_m == 1 then
			suffix = suffix .. equave_n
		else
			suffix = suffix .. equave_ratio
		end
	end
	return { size = size, equave = equave, suffix = suffix }
end

-- parse a ET structure
function p.parse(unparsed)
	local size, suffix, equave = unparsed:match("^(%d+)([Ee][Dd](.+))$")
	if equave == nil then
		return nil
	end
	suffix = suffix:lower()
	size = tonumber(size)
	equave = common_ratio[equave] or rat.parse(equave)
	if size == nil or equave == nil then
		return nil
	end
	return p.new(size, equave, suffix)
end

-- construct a string representation for a ET structure
function p.as_string(et)
	return et.size .. et.suffix
end

-- convert steps to a proper ratio (except that it is a float approximation)
function p.backslash_ratio(et, steps)
	if et.size == 0 then
		return 1
	end
	return rat.as_float(et.equave) ^ (steps / et.size)
end

function p.backslash_display(et, steps)
	if et.size == 0 then
		return 1
	end
	return steps .. p.backslash_modifier(et)
end

function p.backslash_modifier(et)
	if not rat.eq(et.equave, 2) then
		return "\\" .. et.size .. et.suffix
	end
	return "\\" .. et.size
end

-- convert steps to cents
function p.cents(et, steps)
	if et.size == 0 then
		return 0
	end
	steps = steps or 1
	return 1200 * steps / et.size * math.log(rat.as_float(et.equave)) / math.log(2)
end

-- convert ratio to steps
-- ratio is a float!
-- towards is one of: -1 (floor), 0 (nearest), 1 (ceil)
function p.approximate(et, ratio, towards)
	towards = towards or 0
	if et.size == 0 then
		return 0
	end
	local exact = math.log(ratio) / math.log(rat.as_float(et.equave)) * et.size
	if towards < 0 then
		return math.floor(exact)
	elseif towards > 0 then
		return math.ceil(exact)
	else
		return math.floor(exact + 0.5)
	end
end

-- whether this ET tempers out the provided rational number
function p.tempers_out(et, ratio)
	local t = 0
	for factor, power in pairs(ratio) do
		if type(factor) == "number" then
			t = t + power * p.approximate(et, factor)
		end
	end
	return t == 0
end

-- determine whether ET is highly composite
function p.is_highly_composite(et)
	et.highly_composite = et.highly_composite or rat.is_highly_composite(et.size)
	return et.highly_composite
end

-- determine whether ET's size could be within one of zeta function-related sequences
function p.is_zeta(et)
	return seq.contains(seq.zeta_peak, et.size)
		or seq.contains(seq.zeta_peak_integer, et.size)
		or seq.contains(seq.zeta_integral, et.size)
		or seq.contains(seq.zeta_gap, et.size)
end

-- describe why
function p.why_zeta(et)
	local zeta_peak = seq.contains(seq.zeta_peak, et.size)
	local zeta_peak_integer = seq.contains(seq.zeta_peak_integer, et.size)
	local zeta_integral = seq.contains(seq.zeta_integral, et.size)
	local zeta_gap = seq.contains(seq.zeta_gap, et.size)

	local markers = {}
	if zeta_peak then
		table.insert(markers, "[[The Riemann zeta function and tuning #Peak EDOs|zeta peak]]")
	elseif zeta_peak == nil then
		table.insert(markers, "[[The Riemann zeta function and tuning #Peak EDOs|zeta peak?]]")
	end

	if zeta_peak_integer then
		table.insert(markers, "[[The Riemann zeta function and tuning #Peak EDOs|zeta peak integer]]")
	elseif zeta_peak_integer == nil then
		table.insert(markers, "[[The Riemann zeta function and tuning #Peak EDOs|zeta peak integer?]]")
	end

	if zeta_integral then
		table.insert(markers, "[[The Riemann zeta function and tuning #Integral of Zeta EDOs|zeta integral]]")
	elseif zeta_integral == nil then
		table.insert(markers, "[[The Riemann zeta function and tuning #Integral of Zeta EDOs|zeta integral?]]")
	end

	if zeta_gap then
		table.insert(markers, "[[The Riemann zeta function and tuning #Zeta Gap EDOs|zeta gap]]")
	elseif zeta_gap == nil then
		table.insert(markers, "[[The Riemann zeta function and tuning #Zeta Gap EDOs|zeta gap?]]")
	end

	return table.concat(markers, "<br>")
end

return p