モジュール:Infobox interval

提供: Xenharmonic Wiki
ナビゲーションに移動 検索に移動

このモジュールについての説明文ページを モジュール:Infobox interval/doc に作成できます

local p = {}
local he = require("Module:Harmonic entropy")
local infobox = require("Module:Infobox")
local rat = require("Module:Rational")
local utils = require("Module:Utils")
local yesno = require("Module:Yesno")

-- check whether the input is a non-empty string
local function value_provided(s)
	return type(s) == "string" and #s > 0
end

function p.infobox_interval(frame)
	local debug_mode = yesno(frame.args["debug"])

	local page_name = frame:preprocess("{{PAGENAME}}")

	local rational = false
	local small = false -- numerator and denominator can be represented as Lua numbers, or irrational
	local regular = false -- finite and greater than zero

	local ratio = nil
	local cents = nil
	local ket = nil
	local ratio_string = nil

	local infobox_data = {}
	local cats = ""

	-- intervals with relatively small powers
	if value_provided(frame.args["Ratio"]) then
		ratio = rat.parse(frame.args["Ratio"])
		if ratio ~= nil then
			rational = true
			small = true
			regular = not ratio.nan and not ratio.inf and not ratio.zero and ratio.sign > 0
			cents = rat.cents(ratio)
			ket = rat.as_ket(ratio, frame)
			ratio_string = rat.as_ratio(ratio)
		end
	end

	-- intervals with large powers
	if ratio == nil and value_provided(frame.args["Ket"]) then
		ratio = rat.from_ket(frame.args["Ket"])
		if ratio ~= nil then
			rational = true
			small = false
			regular = true
			cents = rat.cents(ratio)
			ket = rat.as_ket(ratio, frame)
			-- display Ratio unless it is page name, in which case it is probably a fallback -- what does this mean?
			if frame.args["Ratio"] ~= page_name then
				ratio_string = frame.args["Ratio"] or ""
			end
		end
	elseif ratio ~= nil and value_provided(frame.args["Ket"]) then
		cats = cats .. "[[Category:Todo:remove explicit ket notation]]"
	end

	-- irrational intervals
	if ratio == nil and value_provided(frame.args["Cents"]) then
		cents = tonumber(frame.args["Cents"])
		if cents ~= nil then
			rational = false
			small = true
			regular = true
			if value_provided(frame.args["Ket"]) then
				ket = frame.args["Ket"]
			end
			-- Ratio is LaTeX unless it is page name, in which case it is probably a fallback -- what does this mean?
			if frame.args["Ratio"] ~= page_name then
				ratio_string = frame.args["Ratio"] or ""
			else
				cats = cats .. "[[Category:Todo:add interval ratio]]"
			end
		end
	elseif ratio ~= nil and value_provided(frame.args["Cents"]) then
		cats = cats .. "[[Category:Todo:remove explicit cents]]"
	end

	if not (regular or rational) then
		cats = cats .. "[[Category:Todo:initialise interval]]"
	end

	-- categorize by rationality and prime limit
	if regular then
		if rational then
			local prime_limit = 2
			if not rat.eq(ratio, 1) then
				prime_limit = rat.max_prime(ratio)
			end
			cats = cats .. "[[Category:有理数音程]]" .. "[[Category:" .. prime_limit .. "-limit音程]]"
		else
			cats = cats .. "[[Category:無理数音程]]"
		end
	end

	local special_properties = {}
	if rational then
		if small and rat.is_superparticular(ratio) then
			if rat.is_square_superparticular(ratio) then
				table.insert(special_properties, "{{en仮リンク|平方スーパーパーティキュラー|square superparticular}}")
			else
				table.insert(special_properties, "[[スーパーパーティキュラー]]")
			end
			cats = cats .. "[[Category:スーパーパーティキュラー]]"
		end
		if rat.is_reduced(ratio, 2, not small) then
			table.insert(special_properties, "[[オクターブ縮約]]")
		end
		if rat.is_harmonic(ratio) then
			num, den = rat.as_pair (ratio)
			table.insert(special_properties, "[[倍音]]")
			cats = cats .. "[[Category:倍音|" .. string.rep("#", string.len(num)) .. "]]"
			if rat.is_prime(ratio) then
				table.insert(special_properties, "[[素数倍音]]")
				cats = cats .. "[[Category:素数倍音|" .. string.rep("#", string.len(num)) .. "]]"
			end
			if rat.is_highly_composite(ratio) then
				table.insert(special_properties, "[[高度合成数倍音]]")
				cats = cats .. "[[Category:倍音|" .. string.rep("#", string.len(num)) .. "]]"
			end
		elseif rat.is_harmonic(ratio, true, not small) then
			table.insert(special_properties, "[[Harmonic|縮約倍音]]")
			cats = cats .. "[[Category:オクターブ縮約倍音]]"
		elseif rat.is_subharmonic(ratio, true, not small) then
			table.insert(special_properties, "[[Subharmonic|縮約下方倍音]]")
			cats = cats .. "[[Category:オクターブ縮約下方倍音]]"
		end
	elseif regular then
		if cents >= 0 and cents < 1200 then
			table.insert(special_properties, "[[オクターブ縮約]]")
		end
	end

	if value_provided(ratio_string) then
		if rational then
			table.insert(infobox_data, {
				"比率",
				ratio_string,
			})
		else
			table.insert(infobox_data, {
				"式",
				"<math>" .. ratio_string .. "</math>",
			})
		end
	end
	if regular and rational then
		if ket:match("<sup>") then
			-- there was a subsequence of 4+ zeros
			table.insert(infobox_data, {
				"[[Subgroup monzos and vals|Subgroup monzo]]",
				rat.as_subgroup_ket(ratio, frame),
			})
		else
			table.insert(infobox_data, {
				"素因数分解",
				rat.factorisation(ratio),
			})
			table.insert(infobox_data, {
				"[[モンゾ]]",
				ket,
			})
		end
	elseif rational then
		table.insert(infobox_data, {
			"素因数分解",
			rat.factorisation(ratio),
		})
	elseif value_provided(ket) then
		-- irrational ket is provided:
		table.insert(infobox_data, {
			"[[モンゾ]]",
			frame:expandTemplate({
				title = "Monzo",
				args = { ket },
			}),
		})
	end
	if regular then
		table.insert(infobox_data, {
			"[[セント]]数",
			utils._round(cents, 7) .. "¢",
		})
	end

	local name = frame.args["Name"]
	if value_provided(name) then
		local caption = "名前"
		if name:match(",") then
			-- removing manual line breaks
			local matches
			name, matches = name:gsub("<br%s*/?>", "")
			if matches > 0 then
				cats = cats .. "[[Category:Todo:remove manual line breaks]]"
			end
			-- removing whitespaces after commas
			name = name:gsub(",%s+", ",")
			-- placing line breaks after commas
			name = name:gsub(",", ",<br>")
		end
		table.insert(infobox_data, {
			caption,
			name,
		})
	else
		cats = cats .. "[[Category:Todo:add interval name]]"
		table.insert(infobox_data, {
			"名前",
			"<abbr title=\"missing value for parameter 'Name'\">''missing''</abbr><sup>[[Template:Infobox Interval| ?&nbsp;]]</sup>",
		})
	end

	local colour_name = frame.args["Color name"]
	if value_provided(colour_name) then
		table.insert(infobox_data, {
			"{{en仮リンク|カラーネーム|color name}}",
			colour_name,
		})
	elseif regular and rational then
		cats = cats .. "[[Category:Todo:add color name]]"
	end

	local FJS_name = frame.args["FJS name"]
	if not value_provided(FJS_name) and rational and regular then
		FJS_name = rat.as_FJS(ratio)
	elseif value_provided(FJS_name) then
		local matches
		FJS_name = FJS_name:gsub("%s", "")
		FJS_name, matches = FJS_name:gsub("<br%s*/?>", "")
		if matches > 0 then
			cats = cats .. "[[Category:Todo:remove manual line breaks]]"
		end
		FJS_name, matches = FJS_name:gsub("<sup>(.*)</sup>", "^{%1}")
		if matches > 0 then
			cats = cats .. "[[Category:Todo:replace sup and sub with LaTeX]]"
		end
		FJS_name, matches = FJS_name:gsub("<sub>(.*)</sub>", "_{%1}")
		if matches > 0 then
			cats = cats .. "[[Category:Todo:replace sup and sub with LaTeX]]"
		end
	end
	if value_provided(FJS_name) then
		FJS_name = FJS_name:gsub("^(%w+)", "\\text{%1}")
		FJS_name = FJS_name:gsub("(%-%d+)", "{%1}")
		if #FJS_name <= 200 then
			table.insert(infobox_data, {
				"[[Functional Just System|FJSネーム]]",
				"<math>" .. FJS_name .. "</math>",
			})
		end
	end

	if #special_properties > 0 then
		table.insert(infobox_data, {
			"性質",
			table.concat(special_properties, ",<br>"),
		})
	end

	-- interval complexity
	if rational and regular then
		table.insert(infobox_data, {
			"[[テニーノルム]] <br />(log<sub>2</sub> ''nd'')",
			utils._round(rat.tenney_height(ratio), 6),
		})
		table.insert(infobox_data, {
			"[[ワイルノルム]] <br />(log<sub>2</sub> max(''n'', ''d''))",
			utils._round(rat.weil_height(ratio), 6),
		})
		table.insert(infobox_data, {
			"[[ウィルソンノルム]] <br />(sopfr(''nd''))",
			utils._round(rat.wilson_height(ratio), 6),
		})
	end

	local harmonic_entropy = frame.args["Harmonic entropy"]
	if regular and value_provided(harmonic_entropy) then
		harmonic_entropy_switch = harmonic_entropy:match("^[Yy][Ee][Ss]$")
		if harmonic_entropy_switch then
			table.insert(infobox_data, {
				"[[ハーモニックエントロピー]]<br>(Shannon, <math>\\sqrt{nd}</math>)",
				"~" .. utils._round(he.harmonic_entropy(cents), 6) .. " bits",
			})
		end
	end

	local is_comma = value_provided(frame.args["Comma"])
	local comma = nil
	if is_comma and regular and cents > 0 then
		-- rational powers are not considered commas
		if not (rational and rat.is_power(ratio)) then
			if cents <= 3.5 then
				comma = "[[Unnoticeable comma|unnoticeable]]"
				cats = cats .. "[[Category:Unnoticeable commas]]"
			elseif cents <= 30 then
				comma = "[[Small comma|small]]"
				cats = cats .. "[[Category:Small commas]]"
			elseif cents <= 100 then
				comma = "[[Medium comma|medium]]"
				cats = cats .. "[[Category:Medium commas]]"
			else
				comma = "[[Large comma|large]]"
				cats = cats .. "[[Category:Large commas]]"
			end
		end
	end
	if comma then
		table.insert(infobox_data, {
			"[[Comma|Comma size]]",
			comma,
		})
	end
	if comma and rational then
		local S_expressions = rat.find_S_expression(ratio)
		if #S_expressions > 0 then
			local caption = "[[S式]]"
			if #S_expressions > 1 then
				caption = caption .. "s"
			end
			table.insert(infobox_data, {
				caption,
				table.concat(S_expressions, ",<br>"),
			})
		end
	end

	local sound = frame.args["Sound"]
	if value_provided(sound) then
		cats = cats .. "[[Category:Pages with internal sound examples]]"
		table.insert(infobox_data, {
			"[[File:" .. sound .. "|270px]]<br><span style=\"font-size: 75%;\">[[:File:" .. sound .. "|<nowiki>[sound info]</nowiki>]]</span>",
		})
	elseif debug_mode and debug_mode ~= "hide" and regular then
		local hz = 2 ^ (math.log(440) / math.log(2) + cents / 1200)
		-- is it within hearing range?
		if hz >= 20 and hz <= 20000 then
			local html_id = "interval_" .. tostring(math.floor(cents))
			table.insert(infobox_data, {
				"<div style=\"display: flex; justify-content: space-around;\"><div style=\"width: 270px; text-align: center;\">"
					.. frame:expandTemplate({
						title = "User:Plumtree/Interval Sound",
						args = {
							Frequency = tostring(hz),
							Center = "true",
							Label = "Audio demonstration",
							Attributes = "id=\"" .. html_id .. "\"",
						},
					})
					.. "<div class=\"sequence-audio-timbre-selector\" data-target=\""
					.. html_id
					.. "\" data-key=\"interval-audio\" data-default=\"semisine\"></div>"
					.. "</div></div>",
			})
		end
	end

	if value_provided(frame.args["Calc"]) or (regular and rational) then
		local query = frame.args["Calc"] or ""
		if not value_provided(query) then
			if small then
				query = ratio_string
			else
				query = "|" .. rat.as_ket(ratio, nil, false, true) .. ">"
			end
		end
		query = mw.uri.encode(query)
		table.insert(infobox_data, {
			"<span style=\"font-size: 75%;\">[https://www.yacavone.net/xen-calc/?q=" .. query .. "''xen-calc''で聴く]</span>",
		})
	end

	local result = infobox.build("<u>音程情報</u>", infobox_data)
	
	if not debug_mode then
		result = result .. cats
	end
	
	return frame:preprocess(result)
end

return p