Module:Chord consistency

Revision as of 13:46, 31 October 2025 by Dummy index (talk | contribs) (delete additively_consistent_int() and use new version of Module:Limits.additively_consistent())
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
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