Module:MOS mode degrees: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
Todo; to distinguish this from mos modes and to follow with current usage of mos mode names, this template will no longer display mode names and mos modes will be used for displaying names
ArrowHead294 (talk | contribs)
mNo edit summary
 
(15 intermediate revisions by 3 users not shown)
Line 1: Line 1:
local p = {}
local mos = require("Module:MOS")
local mos = require("Module:MOS")
local tamnams = require("Module:TAMNAMS")
local tip = require("Module:Template input parse")
local tip = require("Module:Template input parse")
local tamnams = require("Module:TAMNAMS")
local yesno = require("Module:Yesno")
local yesno = require("Module:Yesno")
local p = {}


-- TODO
-- TODO
Line 99: Line 100:
table.insert(udps, udps_closest_bright[i])
table.insert(udps, udps_closest_bright[i])
else
else
table.insert(udps, string.format("%s<br>%s", udps_closest_bright[i], udps_closest_dark[i]))
table.insert(udps, string.format("%s<br />%s", udps_closest_bright[i], udps_closest_dark[i]))
end
end
table.insert(cpos, i)
table.insert(cpos, i)
Line 111: Line 112:
-- Table's title
-- Table's title
-- If it's for a modmos, add the step pattern
-- If it's for a modmos, add the step pattern
local title = string.format("Scale degrees of the modes of %s", scale_sig) .. (is_true_mos and "&nbsp;" or string.format(" (%s)&nbsp;", step_pattern))
result = result .. "|+ style=\"font-size: 105%; white-space: nowrap;\" | " .. string.format("Scale degrees of the modes of %s", scale_sig)
result = result .. "|+ style=\"font-size: 105%; white-space: nowrap;\" | " .. string.format("%s\n", title)
.. (is_true_mos and "\n" or string.format(" (%s)\n", step_pattern))
.. "|-\n"
.. "|-\n"
-- Add table headers for first row
-- Add table headers for first row
result = result
result = result
.. "! rowspan=\"2\" | UDP " .. (is_true_mos and "" or " and<br>alterations ") -- If modmos, add "and alterations" string
.. "! rowspan=\"2\" | UDP" .. (is_true_mos and "\n" or " and<br />alterations\n") -- If modmos, add "and alterations" string
.. "!! rowspan=\"2\" | Rotational<br>Order "
.. "! rowspan=\"2\" | Cyclic<br />order\n"
.. "!! rowspan=\"2\" | Step<br>Pattern"
.. "! rowspan=\"2\" | Step<br />pattern\n"
-- Add header for scale degrees
-- Add header for scale degrees
result = result .. string.format(" !! colspan=\"%d\" class=\"unsortable\" | Scale Degree (%sdegree)\n", #step_matrices[1], mos_prefix)
result = result .. string.format("! colspan=\"%d\" class=\"unsortable\" | Scale degree (%sdegree)\n", #step_matrices[1], mos_prefix)
-- Add second row of headers
-- Add second row of headers
Line 128: Line 129:
.. "! 0"
.. "! 0"
for i = 1, #step_patterns[1] do
for i = 1, #step_patterns[1] do
result = result .. string.format(" !! %d", i)
result = result .. string.format("\n! %d", i)
end
end
Line 138: Line 139:
-- Add brightness order (as UDP), rotational order, and step pattern
-- Add brightness order (as UDP), rotational order, and step pattern
.. string.format("| %s || %s || %s", udps[i], cpos[i], step_patterns[i])
.. string.format("| %s\n| %s\n| %s\n", udps[i], cpos[i], step_patterns[i])


-- Add scale degrees with cell coloring
-- Add scale degrees with cell coloring
Line 146: Line 147:
local cell_color = p.cell_color(current_interval, input_mos)
local cell_color = p.cell_color(current_interval, input_mos)
local style_code = cell_color == p.cell_color_none and "" or string.format("style=\"background: %s;\" | ", cell_color)
local style_code = (cell_color == p.cell_color_none and "" or string.format("style=\"background: %s;\" | ", cell_color))
result = result .. string.format(" || %s%s", style_code, degree_quality)
result = result .. string.format("| %s%s\n", style_code, degree_quality)
end
end
result = result .. "\n"
end
end
Line 166: Line 166:
local step_pattern = frame.args["MODMOS Step Pattern"]
local step_pattern = frame.args["MODMOS Step Pattern"]
local mode_names_unparsed = frame.args["Mode Names"]
local mode_names_unparsed = frame.args["Mode Names"]
-- Parse debugging option
local debugg = yesno(frame.args["debug"])
-- Get the scale sig; for calculating the mos prefix
-- Get the scale sig; for calculating the mos prefix
Line 186: Line 189:
-- Closest-mode search always returns one name
-- Closest-mode search always returns one name
mode_names = {
mode_names = {
"Harmonic minor<br>(Aeolian ♮7)",
"Harmonic minor<br />(Aeolian ♮7)",
"Locrian ♮6",
"Locrian ♮6",
"Ionian augmented<br>(Ionian ♯5)",
"Ionian augmented<br />(Ionian ♯5)",
"Dorian ♯4",
"Dorian ♯4",
"Phrygian dominant<br>(Phrygian ♮3)",
"Phrygian dominant<br />(Phrygian ♮3)",
"Lydian ♯2",
"Lydian ♯2",
"Altered diminished<br>(Locrian ♭4 𝄫7)",
"Altered diminished<br />(Locrian ♭4 𝄫7)",
}
}
elseif step_pattern == "LLsLsAs" then
elseif step_pattern == "LLsLsAs" then
Line 198: Line 201:
-- Closest-mode search always returns one name
-- Closest-mode search always returns one name
mode_names = {
mode_names = {
"Harmonic major<br>(Ionian ♭6)",
"Harmonic major<br />(Ionian ♭6)",
"Dorian ♭5",
"Dorian ♭5",
"Phrygian ♭4",
"Phrygian ♭4",
"Lydian ♭3",
"Lydian ♭3",
"Mixolydian ♭2",
"Mixolydian ♭2",
"Lydian augmented ♯2<br>(Lydian ♯2 ♯5)",
"Lydian augmented ♯2<br />(Lydian ♯2 ♯5)",
"Locrian 𝄫7",
"Locrian 𝄫7",
}
}
Line 210: Line 213:
-- Closest-mode search sometimes returns two names
-- Closest-mode search sometimes returns two names
mode_names = {
mode_names = {
"Melodic minor<br>(Ionian ♭3, Dorian ♮7)",
"Melodic minor<br />(Ionian ♭3, Dorian ♮7)",
"Dorian ♭2, Phrygian ♮6",
"Dorian ♭2, Phrygian ♮6",
"Lydian augmented<br>(Lydian ♯5)",
"Lydian augmented<br />(Lydian ♯5)",
"Lydian dominant<br>(Lydian ♭7, Mixolydian ♯4)",
"Lydian dominant<br />(Lydian ♭7, Mixolydian ♯4)",
"Mixolydian ♭6, Aeolian ♮3",
"Mixolydian ♭6, Aeolian ♮3",
"Half-diminished<br>(Aeolian ♭5, Locrian ♮2)",
"Half-diminished<br />(Aeolian ♭5, Locrian ♮2)",
"Altered, Altered dominant<br>(Locrian ♭4)",
"Altered, Altered dominant<br />(Locrian ♭4)",
}
}
elseif step_pattern == "sLLLLLs" then
elseif step_pattern == "sLLLLLs" then
Line 222: Line 225:
-- Closest-mode search sometimes returns two names
-- Closest-mode search sometimes returns two names
mode_names = {
mode_names = {
"Neapolitan major<br>(Ionian ♭2 ♭3, Phrigian ♮6 ♮7)",
"Neapolitan major<br />(Ionian ♭2 ♭3, Phrigian ♮6 ♮7)",
"Lydian augmented ♯6<br>(Lydian ♯5 ♯6)",
"Lydian augmented ♯6<br />(Lydian ♯5 ♯6)",
"Lydian augmented dominant<br>(Lydian ♯5 ♭7, Mixolydian ♯4 ♯5)",
"Lydian augmented dominant<br />(Lydian ♯5 ♭7, Mixolydian ♯4 ♯5)",
"Lydian minor<br>(Lydian ♭6 ♭7, Aeolian ♮3 ♯4)",
"Lydian minor<br />(Lydian ♭6 ♭7, Aeolian ♮3 ♯4)",
"Major locrian<br>(Mixolydian ♭5 ♭6, Locrian ♮2 ♮3)",
"Major locrian<br />(Mixolydian ♭5 ♭6, Locrian ♮2 ♮3)",
"Altered dominant ♮2<br>(Aeolian ♭4 ♭5, Locrian ♮2, ♭4)",
"Altered dominant ♮2<br />(Aeolian ♭4 ♭5, Locrian ♮2, ♭4)",
"Altered dominant 𝄫3<br>(Locrian 𝄫3 ♭4)",
"Altered dominant 𝄫3<br />(Locrian 𝄫3 ♭4)",
}
}
elseif step_pattern == "sLLLsAs" then
elseif step_pattern == "sLLLsAs" then
Line 234: Line 237:
-- Closest-mode search always returns one name
-- Closest-mode search always returns one name
mode_names = {
mode_names = {
"Neapolitan minor<br>(Phrygian ♮7)",
"Neapolitan minor<br />(Phrygian ♮7)",
"Lydian ♯6",
"Lydian ♯6",
"Mixolydian augmented<br>(Mixolydian ♯5)",
"Mixolydian augmented<br />(Mixolydian ♯5)",
"Aeolian ♯4",
"Aeolian ♯4",
"Locrian dominant<br>(Locrian ♮3)",
"Locrian dominant<br />(Locrian ♮3)",
"Ionian ♯2",
"Ionian ♯2",
"Altered diminished 𝄫3<br>(Locrian 𝄫3 ♭4 𝄫7)",
"Altered diminished 𝄫3<br />(Locrian 𝄫3 ♭4 𝄫7)",
}
}
elseif step_pattern == "sAsLsAs" then
elseif step_pattern == "sAsLsAs" then
Line 246: Line 249:
-- Closest-mode search sometimes returns two names
-- Closest-mode search sometimes returns two names
mode_names = {
mode_names = {
"Double harmonic<br>(Ionian ♭2 ♭6, Phrygian ♮3 ♮7)",
"Double harmonic<br />(Ionian ♭2 ♭6, Phrygian ♮3 ♮7)",
"Lydian ♯2 ♯6",
"Lydian ♯2 ♯6",
"Altered ♮5 𝄫6<br>(Phrygian ♭4 𝄫7)",
"Altered ♮5 𝄫6<br />(Phrygian ♭4 𝄫7)",
"Double harmonic minor<br>(Lydian ♭3 ♭6, Aeolian ♯4 ♮7)",
"Double harmonic minor<br />(Lydian ♭3 ♭6, Aeolian ♯4 ♮7)",
"Mixolydian ♭2 ♭5, Locrian ♮3 ♮6",
"Mixolydian ♭2 ♭5, Locrian ♮3 ♮6",
"Ionian augmented ♯2<br>(Ionian ♯2 ♯5)",
"Ionian augmented ♯2<br />(Ionian ♯2 ♯5)",
"Locrian 𝄫3 𝄫7",
"Locrian 𝄫3 𝄫7",
}
}
Line 292: Line 295:
end
end
return result
-- Debugger option
if debugg == true then
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
end
return frame:preprocess(result)
end
end


return p
return p

Latest revision as of 12:43, 1 June 2025

Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:MOS mode degrees.

This module creates a table of the scale degrees for each mode of a MOS or MODMOS scale.

Introspection summary for Module:MOS mode degrees 
Functions provided (3)
Line Function Params
24 cell_color (interval, input_mos)
59 _mos_mode_degrees (main) (input_mos, mos_prefix, is_collapsed, step_pattern)
162 mos_mode_degrees (invokable) (frame)
Lua modules required (4)
Variable Module Functions used
mos Module:MOS new
period_step_count
interval_step_count
interval_chroma_count
modes_by_brightness
modes_to_step_matrices
mode_rotations
mode_rotations_to_step_matrices
as_string
equave_step_count
parse
tamnams Module:TAMNAMS mos_mode_udps
mos_mode_cpos
mode_rotation_udps
decode_quality
verify_prefix
tip Module:Template input parse parse_entries
yesno Module:Yesno yesno

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


local p = {}

local mos = require("Module:MOS")
local tamnams = require("Module:TAMNAMS")
local tip = require("Module:Template input parse")
local yesno = require("Module:Yesno")

-- TODO
-- - Split off modmos mode degrees as a separate template

-- Global variables for cell colors
-- Colors are as follows:
-- - Orange and blue for small and large sizes, respectively
-- - Darker colors for altered scale degrees
-- - No color for period intervals
p.cell_color_none = "NONE"				-- For cells that don't have a color (default cell color applies)
p.cell_color_perfect_size = "NONE"		-- Only applies for periods, including the root and equave
p.cell_color_lg_altered_size = "#BDD7EE"
p.cell_color_large_size      = "#DDEBF7"
p.cell_color_small_size      = "#FCE4D6"
p.cell_color_sm_altered_size = "#F8CBAD"

-- Finds the row color for a single cell
function p.cell_color(interval, input_mos)
	local interval = interval or {["L"] = 3, ["s"] = 1}
	local input_mos = input_mos or mos.new(5, 2)
	
	local period_step_count = mos.period_step_count(input_mos)
	local interval_step_count = mos.interval_step_count(interval)
	local chroma_count = mos.interval_chroma_count(interval, input_mos)
	
	local is_period_interval = interval_step_count % period_step_count == 0
	
	local color = p.cell_color_none
	if is_period_interval then
		if chroma_count > 0 then
			color = p.cell_color_lg_altered_size
		elseif chroma_count == 0 then
			color = p.cell_color_none
		elseif chroma_count < 0 then
			color = p.cell_color_sm_altered_size
		end
	else
		if chroma_count > 0 then
			color = p.cell_color_lg_altered_size
		elseif chroma_count == 0 then
			color = p.cell_color_large_size
		elseif chroma_count == -1 then
			color = p.cell_color_small_size
		elseif chroma_count < -1 then
			color = p.cell_color_sm_altered_size
		end
	end
	return color
end

-- Create a table of a mos's degrees
-- If a step pattern is provided, it's assumed to be that of a modmos
function p._mos_mode_degrees(input_mos, mos_prefix, is_collapsed, step_pattern)
	local is_true_mos = step_pattern == nil
	local input_mos = input_mos or mos.new(5, 2)
	local mos_prefix = mos_prefix or "mos"
	local is_collapsed = is_collapsed == true
	
	-- Get the modes as strings and step vectors
	local step_patterns = {}
	local step_matrices = {}
	if is_true_mos then
		step_patterns = mos.modes_by_brightness(input_mos)
		step_matrices = mos.modes_to_step_matrices(input_mos)
	else
		step_patterns = mos.mode_rotations(step_pattern)
		step_matrices = mos.mode_rotations_to_step_matrices(step_pattern)
	end
	
	-- Get the scale sig
	local scale_sig = mos.as_string(input_mos)

	-- Equave step count; needed for degree column count
	local equave_step_count = mos.equave_step_count(input_mos)
	
	-- Get the brightness (UDP) and rotational orderings (CPOs).
	-- Also produce default mode names if set to do so.
	local udps = {}
	local cpos = {}
	if is_true_mos then
		-- Get UDPs and CPOs
		udps = tamnams.mos_mode_udps(input_mos)
		cpos = tamnams.mos_mode_cpos(input_mos)
	else
		-- Modmos udps require a mosabbrev; this is forced to be "m" since some
		-- abbrevs are tooo long. Get both the names for the closest-bright and
		-- closest-dark mode. If they're the same, only one name will be used;
		-- if not, both are used.
		-- The CPOs of a modmos are simply 1 to n (n is the mode count).
		local udps_closest_bright = tamnams.mode_rotation_udps(step_pattern, input_mos, "m", true)
		local udps_closest_dark = tamnams.mode_rotation_udps(step_pattern, input_mos, "m", false)
		for i = 1, #udps_closest_bright do
			if udps_closest_bright[i] == udps_closest_dark[i] then
				table.insert(udps, udps_closest_bright[i])
			else
				table.insert(udps, string.format("%s<br />%s", udps_closest_bright[i], udps_closest_dark[i]))
			end
			table.insert(cpos, i)
		end
	end
	
	-- Create table
	local result = "{| class=\"wikitable sortable mw-collapsible center-2 center-3"
		.. (is_collapsed and " mw-collapsed\"" or "\"") .. "\n"
	
	-- Table's title
	-- If it's for a modmos, add the step pattern
	result = result .. "|+ style=\"font-size: 105%; white-space: nowrap;\" | " .. string.format("Scale degrees of the modes of %s", scale_sig)
		.. (is_true_mos and "\n" or string.format(" (%s)\n", step_pattern))
		.. "|-\n"
	
	-- Add table headers for first row
	result = result
		.. "! rowspan=\"2\" | UDP" .. (is_true_mos and "\n" or " and<br />alterations\n")		-- If modmos, add "and alterations" string
		.. "! rowspan=\"2\" | Cyclic<br />order\n"
		.. "! rowspan=\"2\" | Step<br />pattern\n"
	
	-- Add header for scale degrees
	result = result .. string.format("! colspan=\"%d\" class=\"unsortable\" | Scale degree (%sdegree)\n", #step_matrices[1], mos_prefix)
	
	-- Add second row of headers
	result = result .. "|- class=\"unsortable\"\n"
		.. "! 0"
	for i = 1, #step_patterns[1] do
		result = result .. string.format("\n! %d", i)
	end
	
	result = result .. "\n"
	
	-- Add table contents
	for i = 1, #step_patterns do
		result = result .. "|-\n"
		
		-- Add brightness order (as UDP), rotational order, and step pattern
			.. string.format("| %s\n| %s\n| %s\n", udps[i], cpos[i], step_patterns[i])

		-- Add scale degrees with cell coloring
		for j = 1, #step_matrices[i] do
			local current_interval = step_matrices[i][j]
			local degree_quality = tamnams.decode_quality(current_interval, input_mos, "shortened")
			
			local cell_color = p.cell_color(current_interval, input_mos)
			local style_code = (cell_color == p.cell_color_none and "" or string.format("style=\"background: %s;\" | ", cell_color))
			
			result = result .. string.format("| %s%s\n", style_code, degree_quality)
		end
	end
	
	-- End of table
	result = result .. "|}"
	
	return result
end

-- Function to be called as part of a template
function p.mos_mode_degrees(frame)
	-- Get args
	local input_mos    = mos.parse(frame.args["Scale Signature"])
	local mos_prefix   = frame.args["MOS Prefix"]
	local step_pattern = frame.args["MODMOS Step Pattern"]
	local mode_names_unparsed = frame.args["Mode Names"]
	
	-- Parse debugging option
	local debugg = yesno(frame.args["debug"])
	
	-- Get the scale sig; for calculating the mos prefix
	local scale_sig = mos.as_string(input_mos)
	
	-- Verify mosprefix
	mos_prefix = tamnams.verify_prefix(input_mos, mos_prefix)
	
	-- Get the mode names
	local mode_names = nil
	-- Default names for 5L 2s modes and select modmosses.
	-- Names are based on whichever mode is returnd by UDP closest-mode search,
	-- with common names added wherever applicable. Sources include:
	-- - https://www.jazz-guitar-licks.com/ and likely others
	-- - Whatever Wikipedia has cited for the Neapolitan scales
	-- NOTE: these names can be overridden if they don't suffice.
	if scale_sig == "5L 2s" then
		if step_pattern == "LsLLsAs" then
			-- Modes of harmonic minor
			-- Closest-mode search always returns one name
			mode_names = {
				"Harmonic minor<br />(Aeolian ♮7)",
				"Locrian ♮6",
				"Ionian augmented<br />(Ionian ♯5)",
				"Dorian ♯4",
				"Phrygian dominant<br />(Phrygian ♮3)",
				"Lydian ♯2",
				"Altered diminished<br />(Locrian ♭4 𝄫7)",
			}
		elseif step_pattern == "LLsLsAs" then
			-- Modes of harmonic major
			-- Closest-mode search always returns one name
			mode_names = {
				"Harmonic major<br />(Ionian ♭6)",
				"Dorian ♭5",
				"Phrygian ♭4",
				"Lydian ♭3",
				"Mixolydian ♭2",
				"Lydian augmented ♯2<br />(Lydian ♯2 ♯5)",
				"Locrian 𝄫7",
			}
		elseif step_pattern == "LsLLLLs" then
			-- Modes of melodic minor
			-- Closest-mode search sometimes returns two names
			mode_names = {
				"Melodic minor<br />(Ionian ♭3, Dorian ♮7)",
				"Dorian ♭2, Phrygian ♮6",
				"Lydian augmented<br />(Lydian ♯5)",
				"Lydian dominant<br />(Lydian ♭7, Mixolydian ♯4)",
				"Mixolydian ♭6, Aeolian ♮3",
				"Half-diminished<br />(Aeolian ♭5, Locrian ♮2)",
				"Altered, Altered dominant<br />(Locrian ♭4)",
			}
		elseif step_pattern == "sLLLLLs" then
			-- Modes of Neapolitan major
			-- Closest-mode search sometimes returns two names
			mode_names = {
				"Neapolitan major<br />(Ionian ♭2 ♭3, Phrigian ♮6 ♮7)",
				"Lydian augmented ♯6<br />(Lydian ♯5 ♯6)",
				"Lydian augmented dominant<br />(Lydian ♯5 ♭7, Mixolydian ♯4 ♯5)",
				"Lydian minor<br />(Lydian ♭6 ♭7, Aeolian ♮3 ♯4)",
				"Major locrian<br />(Mixolydian ♭5 ♭6, Locrian ♮2 ♮3)",
				"Altered dominant ♮2<br />(Aeolian ♭4 ♭5, Locrian ♮2, ♭4)",
				"Altered dominant 𝄫3<br />(Locrian 𝄫3 ♭4)",
			}
		elseif step_pattern == "sLLLsAs" then
			-- Modes of Neapolitan minor
			-- Closest-mode search always returns one name
			mode_names = {
				"Neapolitan minor<br />(Phrygian ♮7)",
				"Lydian ♯6",
				"Mixolydian augmented<br />(Mixolydian ♯5)",
				"Aeolian ♯4",
				"Locrian dominant<br />(Locrian ♮3)",
				"Ionian ♯2",
				"Altered diminished 𝄫3<br />(Locrian 𝄫3 ♭4 𝄫7)",
			}
			elseif step_pattern == "sAsLsAs" then
			-- Modes of double harmonic
			-- Closest-mode search sometimes returns two names
			mode_names = {
				"Double harmonic<br />(Ionian ♭2 ♭6, Phrygian ♮3 ♮7)",
				"Lydian ♯2 ♯6",
				"Altered ♮5 𝄫6<br />(Phrygian ♭4 𝄫7)",
				"Double harmonic minor<br />(Lydian ♭3 ♭6, Aeolian ♯4 ♮7)",
				"Mixolydian ♭2 ♭5, Locrian ♮3 ♮6",
				"Ionian augmented ♯2<br />(Ionian ♯2 ♯5)",
				"Locrian 𝄫3 𝄫7",
			}
		elseif #step_pattern == 0 then
			-- True-mos modes
			mode_names = { 
				"Lydian",
				"Ionian (major)",
				"Mixolydian",
				"Dorian",
				"Aeolian (minor)",
				"Phrygian",
				"Locrian"
			}
		end
	end
	
	-- If mode names are given, use those instead
	-- If using default mode names (scalesig+udp), those names are auto-added by the relevant function
	local use_default_names = false
	if #mode_names_unparsed ~= 0 then
		if mode_names_unparsed == "Default" then
			use_default_names = true
		else
			mode_names = tip.parse_entries(mode_names_unparsed)
		end
	end
	
	-- Check if the table should start collapsed
	local is_collapsed = yesno(frame.args["Collapsed"], false)
	
	-- If a modmos step pattern was never provided, call the function mos_mode_degrees
	-- Otherwise, call the function modmos_mode_degrees
	local result = ""
	if step_pattern == "" then
		result = p._mos_mode_degrees(input_mos, mos_prefix, is_collapsed)
	--elseif #step_pattern == input_mos.nL + input_mos.ns then
	else
		result = p._mos_mode_degrees(input_mos, mos_prefix, is_collapsed, step_pattern)
	end
	
	-- Debugger option
	if debugg == true then
		result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
	end
	
	return frame:preprocess(result)
end

return p