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

From Xenharmonic Wiki
Jump to navigation Jump to search
ArrowHead294 (talk | contribs)
No edit summary
ArrowHead294 (talk | contribs)
No edit summary
Line 1: Line 1:
bit32 = require("bit32")
bit32 = require("bit32")
utils = require("Module:Utils")
utils = require("Module:Utils")
ET = require("Module:ET")
limits = require("Module:Limits")
limits = require("Module:Limits")


Line 174: Line 175:
local steps = tonumber(frame.args["steps"])
local steps = tonumber(frame.args["steps"])
local limit = math.max(tonumber(frame.args["limit"]), 2)
local limit = math.max(tonumber(frame.args["limit"]), 2)
local constcy = limits.consistency_limit(steps .. "edo", false, 43)
local constcy = limits.consistency_limit(ET.parse(steps .. "edo"), false, 43)
-- local prec = tonumber (frame.args['prec']) or prec_by_equal (steps)
-- local prec = tonumber (frame.args['prec']) or prec_by_equal (steps)
local note = frame.args["note"]
local note = frame.args["note"]

Revision as of 06:01, 24 May 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
174 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}

local function is_in(v, t)
	for i = 1, #t do
		if v == t[i] then
			return true
		end
	end
	return false
end

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_pval = math.abs(approx_size - true_size)
		error_rel_direct = 100 * error_abs_direct / step_size
		error_rel_pval = 100 * error_abs_pval / step_size
		error_list[i] = 
		{
			ratio = ratio, 
			comp = comp, 
			error_abs_direct = error_abs_direct,
			error_abs_pval = error_abs_pval, 
			error_rel_direct = error_rel_direct,
			error_rel_pval = error_rel_pval
		}
	end
	table.sort(error_list, function(a, b) return a.error_abs_pval < b.error_abs_pval end)
	return error_list
end

local function approx(steps, subgroup, monzo_list, t_title)
	local t_body = {}
	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)
		error_abs_pval = string.format("%.3f", error_list[i].error_abs_pval)
		error_rel_pval = string.format("%.1f", error_list[i].error_rel_pval)
		error_abs_direct = string.format("%.3f", error_list[i].error_abs_direct)
		error_rel_direct = string.format("%.1f", error_list[i].error_rel_direct)
		if bit32.band(error_list[i].ratio.den, error_list[i].ratio.den - 1) == 0 and is_in(error_list[i].ratio.num, subgroup) then -- check power of 2 for den and prime for num
			ratiocomp = "'''" .. ratiocomp .. "'''"
			error_abs_pval = "'''" .. error_abs_pval .. "'''"
			error_rel_pval = "'''" .. error_rel_pval .. "'''"
			error_abs_direct = "'''" .. error_abs_direct .. "'''"
			error_rel_direct = "'''" .. error_rel_direct .. "'''"
		end
		if error_list[i].error_rel_pval > 50 then
			ratiocomp = "''" .. ratiocomp .. "''"
			error_abs_pval = "''" .. error_abs_pval .. "''"
			error_rel_pval = "''" .. error_rel_pval .. "''"
			error_abs_direct = "''" .. error_abs_direct .. "''"
			error_rel_direct = "''" .. error_rel_direct .. "''"
		end
		t_body[i] = string.format("|-\n| %s\n| %s\n| %s\n| %s\n| %s", ratiocomp, error_abs_direct, error_rel_direct, error_abs_pval, error_rel_pval)
	end

	return "{| class=\"wikitable center-all mw-collapsible mw-collapsed sortable\"\n" ..
	"|+ style=\"white-space: nowrap;\" | " .. t_title .. "\n" ..
	"|-\n" ..
	"! rowspan=\"2\" class=\"unsortable\" | Interval and complement\n" ..
	"! colspan=\"2\" | Direct approximation<br />(even if inconsistent)\n" ..
	"! colspan=\"2\" | Patent val mapping\n" ..
	"|-\n" ..
	"! Error (abs, [[Cent|¢]])\n" ..
	"! Error (rel, [[Relative cent|%]])\n" ..
	"! Error (abs, [[Cent|¢]])\n" ..
	"! Error (rel, [[Relative cent|%]])\n" ..
	table.concat(t_body, "\n") ..
	"\n|}"
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"]
	if title == nil or #title == 0 then
		title = string.format("%d-odd-limit interval mappings 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 frame.args["header"] ~= "none" then
		out_str = out_str .. "The following table shows how [[" .. frame.args["limit"] .. "-odd-limit intervals]] are represented in " .. steps .. "edo. " ..
		"Octave-reduced prime harmonics are in '''bold'''"
	end
	if constcy >= limit then
		out_str = out_str .. "."
	else
		out_str = out_str .. "; inconsistent intervals are in ''italic''."
	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 specifically in terms of 12edo, the absolute and relative errors are the same.\n\n"
	end
	out_str = out_str .. approx(steps, subgroup, monzo_list, title)
	return out_str
end

return p;