Module:MOS modes

From Xenharmonic Wiki
Revision as of 10:37, 9 October 2025 by Ganaram inukshuk (talk | contribs)
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:MOS modes.

This template is used for mos pages to automatically generate and list that mos's modes, along with the modes' UDP (up-down-period) notation.

Introspection summary for Module:MOS modes 
Functions provided (4)
Line Function Params
23 preprocess_entries (num_modes, args, col_name)
53 _mos_modes (main) (args)
135 modes_table (invokable) (frame)
198 tester none
Lua modules required (7)
Variable Module Functions used
getArgs Module:Arguments getArgs
mos Module:MOS new
modes_by_brightness
as_string
parse
period_step_count
rat Module:Rational dependency not used
tamnams Module:TAMNAMS mos_mode_udps
mos_mode_cpos
tip Module:Template input parse parse_entries
utils Module:Utils trim
yesno Module:Yesno yesno

No function descriptions were provided. The Lua code may have further information.


local getArgs = require("Module:Arguments").getArgs
local mos     = require("Module:MOS")
local rat     = require("Module:Rational")
local tamnams = require("Module:TAMNAMS")
local tip     = require("Module:Template input parse")
local utils   = require("Module:Utils")
local yesno   = require("Module:Yesno")

local p = {}

-- TODO:
-- - (High priority): Refactor code so instead of string concatenation, lines
--   are appended to a table, where table.concat() is called at the end.
-- - Add ability to autocollapse on large mos pages (say, more than 12 modes)
-- - Drop/replace delimiter-based mode name input; it's harder to maintain
-- - Limit table entries to ONLY proposed mode names and MAYBE one supplementary
--   column?! Any more is best done with a subst-instance of the table
-- - Think of better naming scheme for entries

-- Extract column info
-- Mode numbering is by BRIGHT GENS DOWN PER PERIOD
-- Nothing is returned; instead, args is modified
function p.preprocess_entries(num_modes, args, col_name)
	local col_name = col_name or ""
	local col_data = {}
	local is_col_empty = true
	
	for i = 1, num_modes do
		-- Put together key, then use it to get data from args
		local bright_gens_down = i - 1
		local key = string.format("%sD", bright_gens_down) .. (col_name ~= "" and string.format(" %s", col_name) or "")
		local cell = args[key] or ""
		cell = utils.trim(cell)
		
		-- Then transfer data to col_data
		table.insert(col_data, cell)
		
		-- Then remove data from args
		args[key] = nil
		
		-- Check whether data was present; absence of data is expressed as an
		-- empty string.
		is_col_empty = is_col_empty and cell == ""
	end
	
	if not is_col_empty then
		args[string.format("%s Entries", col_name)] = col_data
	end
end

-- "Main" function
-- To be called by wrapper
function p._mos_modes(args)
	local is_collapsed = args["Is Collapsed"] == true
	local input_mos    = args["Input MOS"] or mos.new(5,2)
	local mode_names   = args["Name Entries"] or args["Mode Names"] or nil
	local headers      = args["Table Headers"]
	local entries      = args["Table Entries"]
	
	-- Get UDPs and CPOs
	local udps = tamnams.mos_mode_udps(input_mos)
	local cpos = tamnams.mos_mode_cpos(input_mos)
	
	-- Get the mos's modes
	local mos_modes = mos.modes_by_brightness(input_mos)

	-- Check whether to add mode names
	local add_mode_names = mode_names ~= nil and #mode_names == #mos_modes

	-- Check whether the number of headers times the number of modes equals the
	-- number of entries. Supplementary info can only be added if this condition
	-- is met. Limited to 3 columns of supplementary info.
	local add_columns = #headers > 0 and #entries > 0
	if add_columns then
		add_columns = add_columns and #mos_modes * #headers == #entries and #headers <= 3
	end
	
	-- Table caption
	local scale_sig = mos.as_string(input_mos)
	
	-- Start of table
	local result = "{| class=\"wikitable sortable center-2 center-3 mw-collapsible" .. (is_collapsed and " mw-collapsed\"\n" or "\"\n")
		.. "|+ style=\"font-size: 105%; white-space: nowrap;\" | " .. string.format("Modes of %s\n", scale_sig)
		.. "|-\n"
		
	-- Table headers
	result = result
		.. "! [[UDP]]"
		.. " !! Cyclic<br />order"
		.. " !! Step<br />pattern"
	
	-- Add header for mode names, if provided.
	if add_mode_names then
		result = result .. " !! class=\"unsortable\" | Mode names"
	end
	
	-- Add column headers for supplementary info, if provided.
	if add_columns then
		for i = 1, #headers do
			result = result .. string.format(" !! class=\"unsortable\" | %s", headers[i])
		end
	end
	
	result = result .. "\n"
	
	-- Enter each row
	for i = 1, #mos_modes do
		result = result .. "|-\n"
		
		-- Add the UDP, brightness order, and the mode's step pattern
		result = result .. string.format("| %s || %s || %s",
			udps[i], cpos[i], mos_modes[i])
		
		-- Add the mode's name, if given
		if add_mode_names then
			result = result .. string.format(" || %s", mode_names[i])
		end
		
		-- Add columns if given
		if add_columns then
			for j = 1, #headers do
				local index = (i - 1) * #headers + j
				result = result .. string.format(" || %s", entries[index])
			end
		end
		
		result = result .. "\n"
	end
	
	result = result .. "|}"
	return result
end

-- Wrapper function; to be called by template
function p.modes_table(frame)
	local args = getArgs(frame)
	
	-- Process scalesig to input mos
	local scale_sig = args["Scale Signature"]
	local input_mos = mos.parse(scale_sig)
	args["Input MOS"] = input_mos
	args["Scale Signature"] = nil
	
	-- Get collapse options
	args["Collapsed"] = yesno(args["Collapsed"], false)
	
	-- Get mode names entered
	-- Arg names are formatted as "<bright-gens-down>D <col-name>"
	-- These are placed in a table called "<col-name> Data"
	local num_modes = mos.period_step_count(input_mos)
	p.preprocess_entries(num_modes, args, "Name")
	
	-- Get the mos's mode names, if given
	-- Mode names are entered as a semicolon-delimited list
	-- 5L 2s gets default names
	local mode_names = nil
	if scale_sig == "5L 2s" then
		args["Mode Names"] = { 
			"Lydian",
			"Ionian (major)",
			"Mixolydian",
			"Dorian",
			"Aeolian (minor)",
			"Phrygian",
			"Locrian"
		}
		args["Name Entries"] = args["Mode Names"]
	end
	
	-- Temporarily re-allow previous input method
	if args["Mode Names"] ~= nil then
		args["Mode Names"] = tip.parse_entries(args["Mode Names"], "$")
	end
	if args["Table Headers"] ~= nil then
		args["Table Headers"] = tip.parse_entries(args["Table Headers"], "$")
	end
	if args["Table Entries"] ~= nil then
		args["Table Entries"] = tip.parse_entries(args["Table Entries"], "$")
	end

	local result = p._mos_modes(args)
	
	-- Current means of adding entries is unmaintainable; to be deprecated.
	if headers_unparsed ~= "" and entries_unparsed ~= "" then
		result = result .. "[[Category:Pages with deprecated template parameters]]"
	end
	
	-- Debugger option
	local debugg = yesno(args["debug"])
	if debugg == true then
		result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
	end
	
	return frame:preprocess(result)
end

-- Tester
function p.tester()
	
	local args = {
		["0D Name"] = "AAA",
		["1D Name"] = "BBB",
		["2D Name"] = "CCC", 
		["3D Name"] = "DDD"
	}
	
	p.preprocess_entries(5, args, "Name")
	
	return args
end

return p