Module:Chord consistency: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Dummy index (talk | contribs)
improve ellipsis
Dummy index (talk | contribs)
delete additively_consistent_int() and use new version of Module:Limits.additively_consistent()
 
(4 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):
--  approx(a*b) = approx(a) + approx(b) forall a, b: a, b, ab in ratios
-- `distinct`: whether distinct ratios are required to be mapped to distinct approximations
-- `previous`: already computed ratios for the previous iteraton
function p.additively_consistent_int(et, ratios, distinct, previous)
distinct = distinct or false
previous = previous or {}
if distinct then
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
for a_key, a in pairs(ratios) 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
if type(distinct) == 'number' then
return true
end
local previous_ordered = {}
for a_key, a in pairs(previous) do
table.insert(previous_ordered, a)
end
local ratios_ordered = {}
for a_key, a in pairs(ratios) do
table.insert(ratios_ordered, a)
end
for i, a in ipairs(ratios_ordered) do
local a_approx = ET.approximate(et, rat.as_float(a))
for j, b in ipairs(previous_ordered) do
local b_approx = ET.approximate(et, rat.as_float(b))
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
for j, b in ipairs(ratios_ordered) do
if i <= j then
local b_approx = ET.approximate(et, rat.as_float(b))
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
end
end
return true
end


-- determine maximum error
-- determine maximum error
Line 101: Line 21:
distance = distance or 1.0
distance = distance or 1.0
ed = ed or 'edo'
ed = ed or 'edo'
maxlen = maxlen or 72
local max_n = 72
maxlen = maxlen or max_n
if max_n < maxlen then max_n = maxlen end
local all_interval = {}
local all_interval = {}


Line 115: Line 37:


local vals = {}
local vals = {}
for i = 1, 72 do
for i = 1, max_n do
local et = ET.parse('' .. i .. ed)
local et = ET.parse('' .. i .. ed)
local consistent = p.additively_consistent_int(et, all_interval, false, previous)
local consistent = limits.additively_consistent(et, all_interval, false, false, nil)
if consistent then
if consistent then
local maxe = p.max_error(et, all_interval)
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 dist = 0.5/maxe
local up = (dist >= distance)
local up = (dist >= distance)

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