Module:Infobox chord: Difference between revisions
Jump to navigation
Jump to search
Undo revision 184878 by ArrowHead294 (talk). The debug mode is used in several places on the wiki where categories should be disabled Tag: Undo |
m Fix |
||
Line 260: | Line 260: | ||
end | end | ||
return frame:preprocess( | return frame:preprocess(result) | ||
end | end | ||
return p | return p |
Revision as of 18:53, 6 March 2025
Note: Do not invoke this module directly; use the corresponding template instead: Template:Infobox chord.
This module implements {{Infobox chord}}
to generate an infobox providing information about a given chord.
local p = {}
local rat = require("Module:Rational")
local utils = require("Module:Utils")
local consistency = require("Module:Chord consistency")
local infobox = require("Module:Infobox")
local yesno = require("Module:Yesno")
function p.infobox_chord(frame)
local debug_mode = yesno(frame.args["debug"])
local page_name = frame:preprocess("{{PAGENAME}}")
local debug_data = ""
local infobox_data = {}
local cats = ""
local color_names = {}
if utils.value_provided(frame.args["ColorName"]) then
color_name = frame.args["ColorName"]
-- search for ", " not "," because many chord names contain commas, e.g. Cz,y6
for name in (color_name .. ", "):gmatch("(.-), ") do
table.insert(color_names, name)
end
else
cats = cats .. "[[Category:Todo:add color name]]"
end
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
-- 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]
if utils.value_provided(frame.args["Root"]) then
root = tonumber(frame.args["Root"])
assert(root > 0, "invalid root")
end
assert(root > 0, "no harmonics given")
local prime_limit = 1
local lcm = 1
local otonal_odd_limit = 1
local root_interval_links = {}
local step_interval_links = {}
local root_cents_steps = {}
local step_cents = {}
local genus = {}
for i, h in ipairs(harmonics) do
-- compute LCM of all harmonics to use as the denominator in utonal form, if needed
lcm = lcm * h / (utils._gcd(lcm, h))
-- increase otonal odd limit for this harmonic, if needed
local odd = h
while odd > 0 and odd % 2 == 0 do
odd = odd / 2
end
if odd > otonal_odd_limit then
otonal_odd_limit = odd
end
-- increase prime limit for this harmonic, if needed,
-- and increase genus factors if needed
for prime, n in pairs(utils.prime_factorization_raw(h)) do
if prime > prime_limit then
prime_limit = prime
end
if prime > 2 then
local prev = genus[prime] or 0
if n > prev then
genus[prime] = n
end
end
end
-- 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 .. "]]")
local cents_ln2 = 1731.234
-- 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 .. "]]")
table.insert(step_cents, utils._round_dec(cents_ln2 * math.log(step_numer / step_denom)) .. "¢")
end
table.insert(root_cents_steps, utils._round_dec(cents_ln2 * math.log(numer / denom)) .. "¢")
end
local utonal_odd_limit = 1
local subharmonics = {}
for i, h in ipairs(harmonics) do
-- find the subharmonics from the harmonics
local gcd = utils._gcd(lcm, h)
local s = lcm / gcd
table.insert(subharmonics, s)
-- find the utonal odd limit
local s_odd = s
while s_odd > 0 and s_odd % 2 == 0 do
s_odd = s_odd / 2
end
if s_odd > utonal_odd_limit then
utonal_odd_limit = s_odd
end
end
-- intervallic odd limit
local odd_limit = 1
for j, b in ipairs(harmonics) do
for i, a in ipairs(harmonics) do
local gcd = utils._gcd(a, b)
local numer = b / gcd
local denom = a / gcd
while numer % 2 == 0 do
numer = numer / 2
end
if numer > odd_limit then
odd_limit = numer
end
while denom > 0 and denom % 2 == 0 do
denom = denom / 2
end
if denom > odd_limit then
odd_limit = denom
end
end
end
-- genus
local genus_terms = {}
local genus_product = 1
local primes = {}
for prime, _ in pairs(genus) do
table.insert(primes, prime)
end
table.sort(primes)
for i, prime in ipairs(primes) do
local exponent = genus[prime]
if exponent == 1 then
table.insert(genus_terms, prime)
else
table.insert(genus_terms, prime .. "<sup>" .. exponent .. "</sup>")
end
genus_product = genus_product * (prime ^ exponent)
end
-- consistent edos
local distance = tonumber(frame.args["Distance"])
if distance == nil then
if #harmonics >= 5 then
distance = 1.5
elseif #harmonics >= 3 then
distance = 2.0
else
distance = 3.0
end
end
local consistent_edos = consistency.consistent_edos(harmonics, distance, 'edo', 4)
-- compute tag to add for category sort order: as many "#" as the number of digits in the first harmonic
local sort_tag = ""
local first_num = page_name:match("^(%d+):")
if first_num then
first_num = tonumber(first_num)
sort_tag = "|#"
local sort_bound = 10
while sort_bound <= harmonics[1] do
sort_tag = sort_tag .. "#"
sort_bound = sort_bound * 10
end
end
table.insert(infobox_data, {"Harmonics", table.concat(harmonics, ":")})
if (not utils.value_provided(frame.args["Root"])) and (utonal_odd_limit <= otonal_odd_limit or utonal_odd_limit < 1000) then
table.insert(infobox_data, {"Subharmonics", "1/(" .. table.concat(subharmonics, ":") .. ")"})
end
table.insert(infobox_data, {"Intervals from root", table.concat(root_interval_links, " – ")})
table.insert(infobox_data, {"Cents from root", table.concat(root_cents_steps, " ")})
table.insert(infobox_data, {"Step intervals", table.concat(step_interval_links, ", ")})
table.insert(infobox_data, {"Step cents", table.concat(step_cents, ", ")})
-- TODO: category goes here.
if table.getn(color_names) > 0 then
local label = "Color name"
if table.getn(color_names) > 1 then
label = "Color names"
end
table.insert(infobox_data, {"[[Color notation|" .. label .. "]]", table.concat(color_names, "<br />")})
end
if prime_limit < 96 then
table.insert(infobox_data, {"[[Prime limit]]", "[[" .. prime_limit .. "-limit|" .. prime_limit .. "]]"})
cats = cats .. "[[Category:" .. prime_limit .. "-limit chords" .. sort_tag .. "]]"
else
table.insert(infobox_data, {"[[Prime limit]]", prime_limit})
cats = cats .. "[[Category:Just intonation chords" .. sort_tag .. "]]"
end
local genus_data = table.concat(genus_terms, " ⋅ ")
-- append the actual product if it's (arbitrarily) 9 digits or fewer
if genus_product < 1000000000 then
genus_data = genus_data .. " (" .. genus_product .. ")"
end
table.insert(infobox_data, {"[[Euler-Fokker genus|Genus]]", genus_data})
if odd_limit < 32 then
table.insert(infobox_data, {"[[Intervallic odd limit]]", "[[" .. odd_limit .. "-odd-limit|" .. odd_limit .. "]]"})
cats = cats .. "[[Category:" .. odd_limit .. "-odd-limit chords" .. sort_tag .. "]]"
else
table.insert(infobox_data, {"[[Intervallic odd limit]]", odd_limit})
end
table.insert(infobox_data, {"[[Otonal odd limit]]", otonal_odd_limit})
table.insert(infobox_data, {"[[Utonal odd limit]]", utonal_odd_limit})
if consistent_edos ~= "" then
table.insert(infobox_data, {"[[Consistency|Consistent edos]] (''d'' ≥ " .. distance .. ")", "<span style=\"font-size: 75%;\">" .. consistent_edos .. "</span>"})
table.insert(infobox_data, {"<div style=\"font-size: 75%; text-align: right; white-space: nowrap;\">[[Module:Chord consistency/doc|* 2 ≤ ''d'' < 4; ** 4 ≤ ''d'' < 8; *** 8 ≤ ''d'' < 16; …]]</div>"})
else
table.insert(infobox_data, {"[[Consistency|Consistent edos]] (''d'' ≥ " .. distance .. ")", "<small>not exist in the range up to 72</small>"})
end
end
if debug_data ~= "" then
table.insert(infobox_data, {
"Debug",
debug_data,
})
end
local result = infobox.build("<u>Chord information</u>", infobox_data)
if not debug_mode then
result = result .. cats
end
return frame:preprocess(result)
end
return p