Module:Q-odd-limit intervals: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Refactoring
Ideally we should only make the table when called for. Here's the first step to approach that.
Line 188: Line 188:
end
end
local out_str_direct = t_head_0 .. t_title .. tag_direct .. "\n" .. t_head_1 .. table.concat(t_body_direct, "\n") .. t_foot
local out_str_val = t_head_0 .. t_title .. tag_val .. "\n" .. t_head_1 .. table.concat(t_body_val, "\n") .. t_foot
if approx_t == "direct" then
if approx_t == "direct" then
local out_str_direct = t_head_0 .. t_title .. tag_direct .. "\n" .. t_head_1 .. table.concat(t_body_direct, "\n") .. t_foot
return out_str_direct
return out_str_direct
elseif approx_t == "val" then
elseif approx_t == "val" then
local out_str_val = t_head_0 .. t_title .. tag_val .. "\n" .. t_head_1 .. table.concat(t_body_val, "\n") .. t_foot
return out_str_val
return out_str_val
else
return out_str_direct .. "\n\n" .. out_str_val
end
end
end
end
Line 250: Line 247:
out_str = ""
out_str = ""
end
end
out_str = out_str .. approx(steps, subgroup, monzo_list, title, tag, apx)
if utils.value_provided (apx) then
out_str = out_str .. approx(steps, subgroup, monzo_list, title, tag, apx)
else
out_str = out_str .. approx(steps, subgroup, monzo_list, title, tag, "direct") .. approx(steps, subgroup, monzo_list, title, tag, "val")
end
return out_str
return out_str
end
end


return p;
return p;

Revision as of 08:28, 12 June 2024

Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:Q-odd-limit intervals.

This module automatically calculates a given equal-tempered tuning's approximations of intervals in a given odd limit, and lists them in a table.

Currently, odd limits up to and including 63 and prime limits up to and including 61 are supported.

Introspection summary for Module:Q-odd-limit intervals 
Functions provided (1)
Line Function Params
203 q_odd_limit_intervals (invokable) (frame)
Lua modules required (0)
Variable Module Functions used

No function descriptions were provided. The Lua code may have further information.


bit32 = require("bit32")
utils = require("Module:Utils")
ET = require("Module:ET")
limits = require("Module:Limits")

local p = {}

local PRIME_LIST = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61}

local function table_filter(t, thres)
	for i = 1, #t do
		if t[i] > thres then
			return {unpack(t, 1, i - 1)}
		end
	end
	return t
end

local function inner_product(val, monzo)
	local result = 0
	for i = 1, #val do
		result = result + val[i] * monzo[i]
	end
	return result
end

local function monzo2ratio(monzo, subgroup)
	local num = 1
	local den = 1
	for i = 1, #subgroup do
		if monzo[i] > 0 then
			num = num * subgroup[i]^monzo[i]
		elseif monzo[i] < 0 then
			den = den * subgroup[i]^(-monzo[i])
		end
	end
	return {num = num, den = den}
end

local function ratio2monzo(ratio, subgroup)
	local monzo = {}
	for i = 1, #subgroup do
		monzo[i] = 0
		while ratio.num % subgroup[i] == 0 do
			monzo[i] = monzo[i] + 1
			ratio.num = ratio.num / subgroup[i]
		end while ratio.den % subgroup[i] == 0 do
			monzo[i] = monzo[i] - 1
			ratio.den = ratio.den / subgroup[i]
		end
	end
	return monzo
end

local function monzo2cent(monzo, subgroup)
	local jip = {}
	for i = 1, #subgroup do
		jip[i] = 1200 * utils._log(subgroup[i], 2)
	end
	return inner_product(jip, monzo)
end

local function ratio_8ve_reduction(ratio)
	local oct = math.floor(utils._log(ratio.num / ratio.den, 2))
	if oct > 0 then
		ratio.den = ratio.den * 2^oct
	elseif oct < 0 then
		ratio.num = ratio.num * 2^(-oct)
	end
	return ratio
end

local function odd_limit_monzo_list_gen(limit)
	local monzo_list = {}
	local subgroup = table_filter(PRIME_LIST, limit)
	for num = 1, limit, 2 do
		for den = 1, num, 2 do
			if utils._gcd(num, den) == 1 then
				ratio = ratio_8ve_reduction({num = num, den = den})
				table.insert(monzo_list, ratio2monzo(ratio, subgroup))
			end
		end
	end
	return monzo_list
end

local function find_error(val, subgroup, monzo_list)
	local step_size = 1200/val[1]
	local true_size
	local approx_size
	local error_list = {}
	for i = 1, #monzo_list do
		ratio = monzo2ratio(monzo_list[i], subgroup)
		comp = {2 * ratio.den, ratio.num}
		true_size = monzo2cent(monzo_list[i], subgroup)
		approx_size = step_size * inner_product(val, monzo_list[i])
		nearest_size = math.floor(true_size / step_size + 0.5)
		error_abs_direct = math.abs(nearest_size * step_size - true_size)
		error_abs_val = math.abs(approx_size - true_size)
		error_rel_direct = 100 * error_abs_direct / step_size
		error_rel_val = 100 * error_abs_val / step_size
		error_list[i] = 
		{
			ratio = ratio, 
			comp = comp, 
			error_abs_direct = error_abs_direct,
			error_abs_val = error_abs_val, 
			error_rel_direct = error_rel_direct,
			error_rel_val = error_rel_val
		}
	end
	table.sort(error_list, function(a, b) return a.error_abs_val < b.error_abs_val end)
	return error_list
end

local function approx(steps, subgroup, monzo_list, t_title, tag, approx_t)
	local t_body_direct = {}
	local errlist_direct = {}
	local t_body_val = {}
	local errlist_val = {}
	local val = {}

	for i = 1, #subgroup do
		val[i] = utils._round_dec(steps*utils._log(subgroup[i], 2))
	end
	error_list = find_error(val, subgroup, monzo_list)
	for i = 1, #error_list do
		ratiocomp = string.format("%d/%d, %d/%d", error_list[i].ratio.num, error_list[i].ratio.den, 2 * error_list[i].ratio.den, error_list[i].ratio.num)
		eav = error_list[i].error_abs_val
		erv = error_list[i].error_rel_val
		ead = error_list[i].error_abs_direct
		erd = error_list[i].error_rel_direct
		error_abs_val = string.format("%.3f", eav)
		error_rel_val = string.format("%.1f", erv)
		error_abs_direct = string.format("%.3f", ead)
		error_rel_direct = string.format("%.1f", erd)
		if bit32.band(error_list[i].ratio.den, error_list[i].ratio.den - 1) == 0 and utils.table_contains(subgroup, error_list[i].ratio.num) then -- check power of 2 for den and prime for num
			ratiocomp = "'''" .. ratiocomp .. "'''"
			error_abs_val = "'''" .. error_abs_val .. "'''"
			error_rel_val = "'''" .. error_rel_val .. "'''"
			error_abs_direct = "'''" .. error_abs_direct .. "'''"
			error_rel_direct = "'''" .. error_rel_direct .. "'''"
		end
		if error_list[i].error_rel_val > 50 then
			ratiocomp = "''" .. ratiocomp .. "''"
			error_abs_val = "''" .. error_abs_val .. "''"
			error_rel_val = "''" .. error_rel_val .. "''"
			error_abs_direct = "''" .. error_abs_direct .. "''"
			error_rel_direct = "''" .. error_rel_direct .. "''"
		end
		
		errlist_direct[i] = {
			ratiocomp = ratiocomp,
			ead = ead,
			erd = erd,
			error_abs_direct = error_abs_direct,
			error_rel_direct = error_rel_direct,
		}
		
		errlist_val[i] = {
			ratiocomp = ratiocomp,
			eav = eav,
			erv = erv,
			error_abs_val = error_abs_val,
			error_rel_val = error_rel_val,
		}
	end
	
	table.sort(errlist_direct, function(a, b) return a.ead < b.ead end)
	
	for i = 1, #error_list do
		t_body_direct[i] = string.format("|-\n| %s || %s || %s", errlist_direct[i].ratiocomp, errlist_direct[i].error_abs_direct, errlist_direct[i].error_rel_direct)
		t_body_val[i] = string.format("|-\n| %s || %s || %s", errlist_val[i].ratiocomp, errlist_val[i].error_abs_val, errlist_val[i].error_rel_val)
	end
	
	local t_head_0 = "{| class=\"wikitable center-all mw-collapsible mw-collapsed sortable\"\n"
		.. "|+ style=\"white-space: nowrap;\" | "
	local t_head_1 = "|-\n"
		.. "! class=\"unsortable\" | Interval and complement !! Error (abs, [[Cent|¢]]) !! Error (rel, [[Relative cent|%]])\n"
	local t_foot = "\n|}"
	
	if tag ~= "none" then
		tag_direct = " (direct approximation, even if inconsistent)"
		tag_val = " (patent val mapping)"
	else
		tag_direct = ""
		tag_val = ""
	end
	
	if approx_t == "direct" then
		local out_str_direct = t_head_0 .. t_title .. tag_direct .. "\n" .. t_head_1 .. table.concat(t_body_direct, "\n") .. t_foot
		return out_str_direct
	elseif approx_t == "val" then
		local out_str_val = t_head_0 .. t_title .. tag_val .. "\n" .. t_head_1 .. table.concat(t_body_val, "\n") .. t_foot
		return out_str_val
	end
end

-- local function prec_by_equal (steps)
-- 	return math.floor (math.log (steps*1.9)/math.log (10))
-- end

function p.q_odd_limit_intervals(frame)
	local steps = tonumber(frame.args["steps"])
	local limit = math.max(tonumber(frame.args["limit"]), 2)
	local constcy = limits.consistency_limit(ET.parse(steps .. "edo"), false, 43)
-- 	local prec = tonumber (frame.args['prec']) or prec_by_equal (steps)
	local note = frame.args["note"]
	local title = frame.args["title"]
	local header = string.lower(frame.args["header"]:gsub("%s+", ""))
	local tag
	local apx = string.lower(frame.args["apx"]:gsub("%s+", ""))
	
	if not utils.value_provided (title) then
		title = string.format("%d-odd-limit intervals in " .. steps .. "edo", limit)
	end
	
	local subgroup = table_filter(PRIME_LIST, limit)
	local monzo_list = odd_limit_monzo_list_gen(limit)
	local out_str
	if header ~= "none" then
		out_str = "The following "
		if constcy >= limit then
			apx = "val"
		end
		if apx == "direct" or apx == "val" then
			out_str = out_str .. "table shows"
		else
			out_str = out_str .. "tables show"
		end
		out_str = out_str .. " how [[" .. limit .. "-odd-limit]] intervals are represented in " .. steps .. "edo. Prime harmonics are in '''bold'''"
		if constcy >= limit then
			out_str = out_str .. ".\n\nAs " .. steps .. "edo is consistent in the " .. limit .. "-odd-limit, the mappings by direct approximation and through the patent val are identical."
			tag = "none"
		else
			out_str = out_str .. "; inconsistent intervals are in ''italics''."
		end
		out_str = out_str .. "\n\n"
		if note ~= "" then
			out_str = out_str .. note .. "\n\n"
		end
		if steps == 12 then
			out_str = out_str .. "Note that since the [[cent]] was defined in terms of 12edo, the absolute and relative errors for 12edo are identical.\n\n"
		end
	else
		tag = string.lower(frame.args["tag"]:gsub("%s+", ""))
		out_str = ""
	end
	
	if utils.value_provided (apx) then
		out_str = out_str .. approx(steps, subgroup, monzo_list, title, tag, apx)
	else
		out_str = out_str .. approx(steps, subgroup, monzo_list, title, tag, "direct") .. approx(steps, subgroup, monzo_list, title, tag, "val")
	end
	return out_str
end

return p;