Module:Chord consistency: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Dummy index (talk | contribs)
equave-free veresion of additively_consistent()
Dummy index (talk | contribs)
delete additively_consistent_int() and use new version of Module:Limits.additively_consistent()
 
(8 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local limits = require('Module:Limits')
local ET = require('Module:ET')
local rat = require('Module:Rational')
local rat = require('Module:Rational')
local utils = require("Module:Utils")
local utils = require("Module:Utils")
local ET = require('Module:ET')
local p = {}
local p = {}


-- check additive consistency for a set of ratios (equave-free version):
-- determine maximum error
--  approx(a*b) = approx(a) + approx(b) forall a, b: a, b, ab in ratios
function p.max_error(et, ratios)
-- `distinct`: whether distinct ratios are required to be mapped to distinct approximations
local maxe = 0.0
-- `previous`: already computed ratios for the previous iteraton
for a_key, a in pairs(ratios) do
function p.additively_consistent_int(et, ratios, distinct, previous)
local a_approx = ET.approximate(et, rat.as_float(a))
distinct = distinct or false
local e = math.abs((ET.cents(et, a_approx) - rat.cents(a)) / ET.cents(et, 1))
previous = previous or {}
if (e > maxe) then
if distinct then
maxe = e
local approx_set = {}
for a_key, a in pairs(previous) do
local a_approx = ET.approximate(et, rat.as_float(a)) % et.size
if approx_set[a_approx] then
if not rat.eq(rat.div(a, approx_set[a_approx]), 1) then
mw.log(a_key .. ' -> ' .. a_approx .. ': conflict!')
return false
end
end
approx_set[a_approx] = a
mw.log(a_key .. ' -> ' .. a_approx)
end
end
for a_key, a in pairs(ratios) do
end
local a_approx = ET.approximate(et, rat.as_float(a)) % et.size
return maxe
if approx_set[a_approx] then
end
if not rat.eq(rat.div(a, approx_set[a_approx]), 1) then
 
mw.log(a_key .. ' -> ' .. a_approx .. ': conflict!')
function p.consistent_edos(harmonics, distance, ed, maxlen)
return false
distance = distance or 1.0
end
ed = ed or 'edo'
local max_n = 72
maxlen = maxlen or max_n
if max_n < maxlen then max_n = maxlen end
local all_interval = {}
 
for i, h in ipairs(harmonics) do
-- compute all ratio
for j, g in ipairs(harmonics) do
if j > i then
local a = rat.new(g, h)
all_interval[rat.as_ratio(a)] = a
end
end
approx_set[a_approx] = a
mw.log(a_key .. ' -> ' .. a_approx)
end
end
end
end
if type(distinct) == 'number' then
 
return true
local vals = {}
end
for i = 1, max_n do
local previous_ordered = {}
local et = ET.parse('' .. i .. ed)
for a_key, a in pairs(previous) do
local consistent = limits.additively_consistent(et, all_interval, false, false, nil)
table.insert(previous_ordered, a)
if consistent then
end
local maxe = p.max_error(et, all_interval)
local ratios_ordered = {}
if maxe <= 5.0e-11 then
for a_key, a in pairs(ratios) do
table.insert(vals, "[[" .. i .. ed .. "]]" .. "(just)")
table.insert(ratios_ordered, a)
break
end
end
for i, a in ipairs(ratios_ordered) do
local dist = 0.5/maxe
local a_approx = ET.approximate(et, rat.as_float(a))
local up = (dist >= distance)
for j, b in ipairs(previous_ordered) do
local llevel = 0
local b_approx = ET.approximate(et, rat.as_float(b))
while (dist >= 2) do
llevel = llevel + 1
local c = rat.mul(a, b)
dist = dist / 2
local c_approx = ET.approximate(et, rat.as_float(c))
local c_key = rat.as_ratio(c)
if previous[c_key] or ratios[c_key] then
if c_approx ~= a_approx + b_approx then
mw.log('a = ' .. rat.as_ratio(a) .. '; b = ' .. rat.as_ratio(b) .. '; ab = ' .. c_key)
mw.log(a_approx .. ' + ' .. b_approx .. ' != ' .. c_approx)
return false
end
end
end
end
if up then
for j, b in ipairs(ratios_ordered) do
if #vals >= maxlen then
if i <= j then
table.insert(vals, "&hellip;")
local b_approx = ET.approximate(et, rat.as_float(b))
break
local c = rat.mul(a, b)
local c_approx = ET.approximate(et, rat.as_float(c))
local c_key = rat.as_ratio(c)
if previous[c_key] or ratios[c_key] then
if c_approx ~= a_approx + b_approx then
mw.log('a = ' .. rat.as_ratio(a) .. '; b = ' .. rat.as_ratio(b) .. '; ab = ' .. c_key)
mw.log(a_approx .. ' + ' .. b_approx .. ' != ' .. c_approx)
return false
end
end
end
table.insert(vals, "[[" .. i .. ed .. "]]" .. string.rep("*", llevel))
end
end
end
end
end
end
return true
 
return table.concat(vals, ", ")
end
end


function p.noinfobox_chord(frame)
function p.noinfobox_chord(frame)
local page_name = frame:preprocess("{{PAGENAME}}")
local distance = tonumber(frame.args["Distance"])
local debug_data = ""
local debug_data = ""
local infobox_data = {}
local infobox_data = {}
Line 98: Line 78:
assert(h > 0, "invalid harmonic")
assert(h > 0, "invalid harmonic")
table.insert(harmonics, h)
table.insert(harmonics, h)
end
if distance == nil then
if #harmonics >= 5 then
distance = 1.5
elseif #harmonics >= 3 then
distance = 2.0
else
distance = 3.0
end
end
end


Line 116: Line 106:
local root_interval_links = {}
local root_interval_links = {}
local step_interval_links = {}
local step_interval_links = {}
local all_interval = {}
for i, h in ipairs(harmonics) do
for i, h in ipairs(harmonics) do
-- compute ratio of this harmonic relative to the root
-- compute ratio of this harmonic relative to the root
Line 131: Line 120:
local step_denom = prev / step_gcd
local step_denom = prev / step_gcd
table.insert(step_interval_links, "[[" .. step_numer .. "/" .. step_denom .. "]]")
table.insert(step_interval_links, "[[" .. step_numer .. "/" .. step_denom .. "]]")
end
-- compute all ratio
for j, g in ipairs(harmonics) do
if j > i then
local step_gcd = utils._gcd(g, h)
local step_numer = g / step_gcd
local step_denom = h / step_gcd
local a = rat.new(g, h)
all_interval[rat.as_ratio(a)] = a
end
end
end
local vals = {}
for i = 1, 50 do
local et = ET.parse('' .. i .. 'edo')
local consistent = p.additively_consistent_int(et, all_interval, false, previous)
if consistent then
table.insert(vals, "[[" .. i .. "edo]]")
end
end
end
end
cat = "(d >= " .. distance .. ") " .. p.consistent_edos(harmonics, distance, 'edo', 4)
--end
--end
cat = table.concat(vals, ", ")


return cat
return cat

Latest revision as of 13:46, 31 October 2025

Module documentation[view] [edit] [history] [purge]
This module primarily serves as a library for other modules and has no corresponding template.

This module provides some functions which enumerate consistent equal divisions relative to some chord.


Introspection summary for Module:Chord consistency 
Functions provided (3)
Line Function Params Description
8 max_error (et, ratios)
20 consistent_edos (harmonics, distance, ed, maxlen) Output list of consistent edos relative to given chord (Lua table of harmonics) up to 72edo. Different equaves, minimum consistency distance, maximum length of list are specifiable.
68 noinfobox_chord (invokable) (frame) Obsolete. Formerly used for Module:Infobox chord.
Lua modules required (4)
Variable Module Functions used
ET Module:ET approximate
cents
parse
limits Module:Limits additively_consistent
rat Module:Rational as_float
cents
new
as_ratio
utils Module:Utils _gcd

Format of edos list

Output of consistent_edos are links to individual edos with each trailing several asterisks. These indicate consistency distace d briefly, none as 1 ≤ d < 2; * as 2 ≤ d < 4; ** as 4 ≤ d < 8; …


local limits = require('Module:Limits')
local ET = require('Module:ET')
local rat = require('Module:Rational')
local utils = require("Module:Utils")
local p = {}

-- determine maximum error
function p.max_error(et, ratios)
	local maxe = 0.0
	for a_key, a in pairs(ratios) do
		local a_approx = ET.approximate(et, rat.as_float(a))
		local e = math.abs((ET.cents(et, a_approx) - rat.cents(a)) / ET.cents(et, 1))
		if (e > maxe) then
			maxe = e
		end
	end
	return maxe
end

function p.consistent_edos(harmonics, distance, ed, maxlen)
	distance = distance or 1.0
	ed = ed or 'edo'
	local max_n = 72
	maxlen = maxlen or max_n
	if max_n < maxlen then max_n = maxlen end
	local all_interval = {}

	for i, h in ipairs(harmonics) do
		-- compute all ratio
		for j, g in ipairs(harmonics) do
			if j > i then
				local a = rat.new(g, h)
				all_interval[rat.as_ratio(a)] = a
			end
		end
	end

	local vals = {}
	for i = 1, max_n do
		local et = ET.parse('' .. i .. ed)
		local consistent = limits.additively_consistent(et, all_interval, false, false, nil)
		if consistent then
			local maxe = p.max_error(et, all_interval)
			if maxe <= 5.0e-11 then
				table.insert(vals, "[[" .. i .. ed .. "]]" .. "(just)")
				break
			end
			local dist = 0.5/maxe
			local up = (dist >= distance)
			local llevel = 0
			while (dist >= 2) do
				llevel = llevel + 1
				dist = dist / 2
			end
			if up then
				if #vals >= maxlen then
					table.insert(vals, "&hellip;")
					break
				end
				table.insert(vals, "[[" .. i .. ed .. "]]" .. string.rep("*", llevel))
			end
		end
	end

	return table.concat(vals, ", ")
end

function p.noinfobox_chord(frame)
	local distance = tonumber(frame.args["Distance"])
	local debug_data = ""
	local infobox_data = {}
	local cats = ""

	--if utils.value_provided(frame.args["Harmonics"]) then
		local harmonics = {}
		for hs in string.gmatch(frame.args["Harmonics"], "[^:]+") do
			h = tonumber(hs)  -- TODO: support rational entries?
			assert(h > 0, "invalid harmonic")
			table.insert(harmonics, h)
		end

		if distance == nil then
			if #harmonics >= 5 then
				distance = 1.5
			elseif #harmonics >= 3 then
				distance = 2.0
			else
				distance = 3.0
			end
		end

		-- reduce harmonics to simplest terms, in case the user accidentally failed to reduce them
		local gcd = harmonics[1]
		for i, h in ipairs(harmonics) do
			gcd = utils._gcd(gcd, h)
			if gcd == 1 then break end
		end
		if gcd > 1 then
			for i, h in ipairs(harmonics) do
				harmonics[i] = harmonics[i] / gcd
			end
		end

		local root = harmonics[1]

		local root_interval_links = {}
		local step_interval_links = {}
		for i, h in ipairs(harmonics) do
			-- compute ratio of this harmonic relative to the root
			local gcd = utils._gcd(h, root)
			local numer = h / gcd
			local denom = root / gcd
			table.insert(root_interval_links, "[[" .. numer .. "/" .. denom .. "]]")

			-- compute ratio of this harmonic relative to the previous
			if i > 1 then
				local prev = harmonics[i-1]
				local step_gcd = utils._gcd(h, prev)
				local step_numer = h / step_gcd
				local step_denom = prev / step_gcd
				table.insert(step_interval_links, "[[" .. step_numer .. "/" .. step_denom .. "]]")
			end
		end
		
		cat = "(d >= " .. distance .. ") " .. p.consistent_edos(harmonics, distance, 'edo', 4)
	--end

	return cat
end

return p