Module:Harmonics infobox

From Xenharmonic Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:Harmonics infobox/doc

-- Module: Harmonics infobox
-- Infobox for "Harmonics n–2n" and "Subharmonics n–2n" pages.
-- Called through Template: Infobox harmonics and Template: Infobox subharmonics.
local p = {}
local getArgs = require("Module:Arguments").getArgs
local infobox = require("Module:Infobox")
local u = require("Module:Utils")
local yesno = require("Module:Yesno")

local EN_DASH = "–"

-- category names, in one place
local CATEGORY = { harmonics = "Overtone scales", subharmonics = "Undertone scales" }
local CATEGORY_PRIME = { harmonics = "Prime overtone scales", subharmonics = "Prime undertone scales" }

local function cents(num, den)
	return math.floor(math.log(num / den) / math.log(2) * 1200 * 1000 + 0.5) / 1000
end

local function fifth_row(label, num, den)
	return { label, string.format("%d/%d (%sc)", num, den, cents(num, den)) }
end

local function build(frame, kind)
	local args = getArgs(frame)
	local n = tonumber(args[1] or args.n or args.steps)
	if not n or n < 1 or n ~= math.floor(n) then
		return '<strong class="error">Template:Infobox harmonics requires a positive integer, e.g. <code>{{Infobox harmonics|12}}</code>.</strong>'
	end
	local sub = kind == "subharmonics"
	local debug_mode = yesno(args.debug, false)
	local wtext = yesno(args.wtext)

	local sortkey = string.rep("#", string.len(tostring(n)))
	local categories = string.format("[[Category:%s|%s]]", CATEGORY[kind], sortkey)

	local prime = ""
	if u.is_prime(n) then
		prime = " (prime)"
		categories = categories .. string.format("[[Category:%s|%s]]", CATEGORY_PRIME[kind], sortkey)
	end

	local data = {}
	data[#data + 1] = { "Prime factorization", u._prime_factorization(n) .. prime }

	-- the two steps nearest 3/2
	local sharp, flat, exact
	if sub then
		-- degree k has ratio 2n/(2n - k); a fifth needs 2n - k = 4n/3
		local m = math.floor(4 * n / 3)
		if (4 * n) % 3 == 0 then
			exact = { 2 * n, m }
		else
			sharp, flat = { 2 * n, m }, { 2 * n, m + 1 }
		end
	else
		-- degree k has ratio (n + k)/n; a fifth needs n + k = 3n/2
		local k = n + math.ceil(n / 2)
		if n % 2 == 0 then
			exact = { k, n }
		else
			sharp, flat = { k, n }, { k - 1, n }
		end
	end
	if exact then
		data[#data + 1] = fifth_row("Fifth", exact[1], exact[2])
	else
		data[#data + 1] = fifth_row("Dual sharp fifth", sharp[1], sharp[2])
		data[#data + 1] = fifth_row("Dual flat fifth", flat[1], flat[2])
	end

	-- no-wrap: otherwise the caption can break after the en dash
	local title = string.format(
		'<span style="white-space: nowrap;">%s %d%s%d</span>',
		sub and "Subharmonics" or "Harmonics",
		n,
		EN_DASH,
		2 * n
	)
	local result = infobox._infobox({ ["Title"] = title, ["Rows"] = data })
	if not debug_mode then
		result = result .. categories
	end
	if wtext then
		result = '<syntaxhighlight lang="wikitext">' .. result .. "</syntaxhighlight>"
	end
	return frame:preprocess(result)
end

function p.harmonics(frame)
	return build(frame, "harmonics")
end

function p.subharmonics(frame)
	return build(frame, "subharmonics")
end

return p