Module:MOS in EDO allperiods

From Xenharmonic Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:MOS in EDO allperiods/doc

local mos = require('Module:MOS')
local rat = require('Module:Rational')
local utils = require('Module:Utils')
local mosinedo = require('Module:MOS in EDO')
local p = {}

-- Helper function
-- Parses entries from a semicolon-delimited string and returns them in an array
-- TODO: Separate this and related functions (parse_pair and parse_kv_pairs) into its own module, as they're included
-- in various modules at this point, such as: scale tree, mos mdoes
function p.parse_entries(unparsed)
	local parsed = {}
	for entry in string.gmatch(unparsed, '([^;]+)') do
		local trimmed = entry:gsub("^%s*(.-)%s*$", "%1")
		table.insert(parsed, trimmed)		-- Add to array
	end
	return parsed
end

-- Main function
-- Creates tables for every mos in an edo, for every possible period count
function p.mos_in_edo_allperiods(edo, max_number_of_periods, show_subsets, generation_limit, temperaments)
	local edo = edo or 12
	local max_number_of_periods = max_number_of_periods or -1
	local show_subsets = show_subsets == 1
	local generation_limit = generation_limit or edo - 1
	local temperaments = temperaments
	
	-- Include temperament names?
	local show_temperament = temperaments ~= nil --and #temperaments == math.floor(period_in_edosteps / verified_number_of_periods / 2)
	
	-- Temporarily disable entry of temperament names
	-- Temperament names will be entered as a set of key-value pairs instead of an array
	show_temperament = false
	
	-- Keep track of how many section headers have been written thus far
	-- If there are multiperiod mosses, then create an L2 header for multi-
	-- period mosses, then for each successive period count, create an L3
	-- header.
	local multi_period_header_written = false
	
	-- Process max number of periods
	-- If -1 was entered, then the maximum is math.floor(edo / 2) - 1, the default max
	-- Otherwise, make sure that value doesn't exceed the default max
	local default_max = math.floor(edo / 2) - 1
	if max_number_of_periods == -1 then
		max_number_of_periods = default_max
	else
		max_number_of_periods = math.min(default_max, max_number_of_periods)
	end
	
	-- Check whether the generation limit is valid
	-- Technically, any number above the edo works with showing all generations (rows)
	-- If it's -1, then show all generations (period_in_edosteps-1)
	if generation_limit == -1 then
		generation_limit = edo - 1
	end
	
	-- Lead section
	-- TODO: Show period/note/subset limits if applicable
	local result = ""
	result = result .. string.format("This page lists all [[moment of symmetry]] scales in [[%iedo]].\n", edo)
	
	-- Call a for loop that produces a set of tables for each period count
	-- Loop from 1 to half the edo minus 1
	-- Skip mosses with period count of edo/2, since that would immediately be
	-- a degenerate mos, hence the minus 1.
	for j = 1, max_number_of_periods do
		local number_of_periods = j
		if edo % number_of_periods == 0 then
			-- Calculate the starting genpair
			-- If the generator and its complement are the same, skip that genpair by
			-- incrementing the starting generator by 1
			local period_in_edosteps = edo / number_of_periods
			local starting_generator = math.ceil(period_in_edosteps / 2)
			local complement = period_in_edosteps - starting_generator
			if starting_generator == complement then
				starting_generator = starting_generator + 1
			end
			
			-- Add a section header
			if number_of_periods == 1 then
				result = result .. string.format("== Single-period MOS scales ==\n")
			else
				-- If the L2 header wasn't written yet, write that, followed by
				-- the corresponding L3 header. Otherwise, write only the L3
				-- header.
				if not multi_period_header_written then
					result = result .. string.format("== Multi-period MOS scales ==\n")
					result = result .. string.format("=== %i periods ===\n", number_of_periods)
					multi_period_header_written = true
				else
					result = result .. string.format("=== %i periods ===\n", number_of_periods)
				end
			end
			
			-- Add a table for each genpair
			for i = starting_generator, period_in_edosteps - 1 do
				-- Calculate current generators
				local gen = i
				local comp = period_in_edosteps - gen
				
				-- Skip mosses that are supported by subset edos; this happens if the
				-- gen and comp have a gcd > 1; if show_subsets is set to true, then
				-- show them anyway.
				local gcd = utils._gcd(gen, comp)
				local add_table = gcd == 1 or show_subsets
				
				-- Add table
				if add_table then
					-- TODO: Rewrite code here for entry of temperaments as a set of
					-- key-value pairs
					local temperament_index = 1 + i - starting_generator
					if show_temperament then
						result = result .. mosinedo.mos_in_edo_simplified(edo, i, number_of_periods, generation_limit, temperaments[temperament_index])
					else
						result = result .. mosinedo.mos_in_edo_simplified(edo, i, number_of_periods, generation_limit)
					end
				end
			end
		end
	end
	
	-- Auto-add categories
	result = result .. string.format("[[Category:%iedo]]\n", edo)
	result = result .. string.format("[[Category:Lists of scales]]\n")
	result = result .. string.format("[[Category:MOS scales]]")
	
	return result
end

function p.test()
	
	return tonumber("")
end

-- Wrapper function to be called by a template
-- Args (TODO: implement):
-- - EDO: the number of divisions; this should be later set up to extract it from the page's title.
-- - Temperaments: the temperament names entered as key-value pairs, where the key is the genpair separated with a dash.
-- - Show Subsets: set to 1 to show mosses of subset edos, such as 12edo mosses for 24edo.
-- - Period Limit: the Period Limit for mosses to display; default is "15"
-- - Number of Periods: if ever set, then the template will show mosses for a specific period count.
-- - Maximum Number of Steps: if set, mosses with more than this value will not be shown; default is 60.
-- - Hide Step Pattern: if ever set to 1, this disables the step pattern visualization; will automatically set to 1 for mosses over 60edo, unless overridden.
-- Maximum values can be overridden to have no max if set to "-1"
function p.mos_in_edo_allperiods_frame(frame)
	
	local edo = tonumber(frame.args["EDO"])
	local temperaments = p.parse_entries(frame.args["Temperaments"])
	local show_subsets = tonumber(frame.args["Show Subsets"])
	local max_periods = tonumber(frame.args["Period Limit"]) or -1
	local generation_limit = tonumber(frame.args["Generation Limit"]) or -1
	
	local result = ""
	result = p.mos_in_edo_allperiods(edo, max_periods, show_subsets, generation_limit, temperaments)
	
	return result
	
end

return p