Module:Infobox chord: Difference between revisions
Jump to navigation
Jump to search
list genus after prime limit |
ArrowHead294 (talk | contribs) m Alphabetise dependencies |
||
(33 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local consistency = require("Module:Chord consistency") | |||
local infobox = require("Module:Infobox") | |||
local rat = require("Module:Rational") | local rat = require("Module:Rational") | ||
local utils = require("Module:Utils") | local utils = require("Module:Utils") | ||
local | local yesno = require("Module:Yesno") | ||
function p.infobox_chord(frame) | function p.infobox_chord(frame) | ||
local debug_mode = | local debug_mode = yesno(frame.args["debug"]) | ||
local page_name = frame:preprocess("{{PAGENAME}}") | local page_name = frame:preprocess("{{PAGENAME}}") | ||
Line 14: | Line 15: | ||
local cats = "" | local cats = "" | ||
local | 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 | end | ||
else | |||
cats = cats .. "[[Category:Todo:add color name]]" | |||
end | end | ||
Line 43: | Line 33: | ||
table.insert(harmonics, h) | table.insert(harmonics, h) | ||
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 = harmonics[1] | ||
if utils.value_provided(frame.args["Root"]) then | if utils.value_provided(frame.args["Root"]) then | ||
Line 77: | Line 80: | ||
prime_limit = prime | prime_limit = prime | ||
end | end | ||
local prev = genus[prime] or 0 | if prime > 2 then | ||
local prev = genus[prime] or 0 | |||
if n > prev then | |||
genus[prime] = n | |||
end | |||
end | end | ||
end | end | ||
Line 148: | Line 153: | ||
-- genus | -- genus | ||
local genus_terms = {} | local genus_terms = {} | ||
local genus_product = 1 | |||
local primes = {} | local primes = {} | ||
for prime, _ in pairs(genus) do | for prime, _ in pairs(genus) do | ||
Line 155: | Line 161: | ||
for i, prime in ipairs(primes) do | for i, prime in ipairs(primes) do | ||
local exponent = genus[prime] | local exponent = genus[prime] | ||
table.insert(genus_terms, prime .. "<sup>" .. exponent .. "</sup>") | 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 | 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 | -- compute tag to add for category sort order: as many "#" as the number of digits in the first harmonic | ||
Line 171: | Line 195: | ||
end | end | ||
table.insert(infobox_data, {"Harmonics", | 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 | 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, ":") .. ")"}) | table.insert(infobox_data, {"Subharmonics", "1/(" .. table.concat(subharmonics, ":") .. ")"}) | ||
Line 179: | Line 203: | ||
table.insert(infobox_data, {"Step intervals", table.concat(step_interval_links, ", ")}) | table.insert(infobox_data, {"Step intervals", table.concat(step_interval_links, ", ")}) | ||
table.insert(infobox_data, {"Step cents", table.concat(step_cents, ", ")}) | 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 | if prime_limit < 96 then | ||
Line 187: | Line 221: | ||
cats = cats .. "[[Category:Just intonation chords" .. sort_tag .. "]]" | cats = cats .. "[[Category:Just intonation chords" .. sort_tag .. "]]" | ||
end | 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 | if odd_limit < 32 then | ||
Line 193: | Line 233: | ||
cats = cats .. "[[Category:" .. odd_limit .. "-odd-limit chords" .. sort_tag .. "]]" | cats = cats .. "[[Category:" .. odd_limit .. "-odd-limit chords" .. sort_tag .. "]]" | ||
else | else | ||
table.insert(infobox_data, {"[[ | table.insert(infobox_data, {"[[Intervallic odd limit]]", odd_limit}) | ||
end | end | ||
table.insert(infobox_data, {"[[Otonal odd limit]]", otonal_odd_limit}) | table.insert(infobox_data, {"[[Otonal odd limit]]", otonal_odd_limit}) | ||
table.insert(infobox_data, {"[[Utonal odd limit]]", utonal_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 | end | ||
Line 206: | Line 253: | ||
end | end | ||
local | local result = infobox.build("<u>Chord information</u>", infobox_data) | ||
if not debug_mode then | if not debug_mode then | ||
result = result .. cats | |||
end | end | ||
return | |||
return frame:preprocess(result) | |||
end | end | ||
return p | return p |
Latest revision as of 12:18, 19 May 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 consistency = require("Module:Chord consistency")
local infobox = require("Module:Infobox")
local rat = require("Module:Rational")
local utils = require("Module:Utils")
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