Module:MOS degrees: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
Degreechain quality encoding (0=perfect, 1=major, -1=minor, 2=augmented, -2=diminished, etc) now names generators as augmented/perfect/diminished for mosses not of the form nL ns
Ganaram inukshuk (talk | contribs)
Main function now uses new degree function
Line 33: Line 33:
local udp = { tonumber(parsed[1]), tonumber(parsed[2]) }
local udp = { tonumber(parsed[1]), tonumber(parsed[2]) }
return udp
return udp
end
-- Helper function that converts a note name given as a quantity of mossteps
-- and chromas (see gamut function) into a name, such as "C#"
-- To be used in conjunction with the genchain function
function p.mosstep_and_chroma_to_note_name(mossteps, chromas, note_symbol, chroma_symbol)
local note_name = note_symbol
if note['chromas'] < 0 then
note_name = note_name .. string.rep(chroma_symbol, chromas)
elseif note['chromas'] > 0 then
note_name = note_name .. string.rep(chroma_symbol, -1 * chromas)
end
return note_name
end
-- Helper function that converts a scale degree given as a quantity of mossteps
-- and a numeric quality (0=perf, 1=maj, -1=min, 2=aug, -2=dim, etc) into a
-- scale degree
-- To be used in conjunction with the degrees function
-- TODO: add ability to change naming from k-mosstep to mos-(k+1)th, since
-- there are cases where that's favored instead of tamnams
function p.mosstep_and_quality_to_degree(mossteps, quality)
local degree_name = mossteps .. "-mosstep"
if quality == 1 then
degree_name = "Major " .. degree_name
elseif quality == 2 then
degree_name = "Augmented " .. degree_name
elseif quality > 2 then
degree_name = (quality - 1) .. "× augmented " .. degree_name
elseif quality == -1 then
degree_name = "Minor " .. degree_name
elseif quality == -2 then
degree_name = "Diminished " .. degree_name
elseif quality < -2 then
degree_name = (math.abs(quality) - 1) .. "× diminished" .. degree_name
end
return degree_name
end
end


Line 63: Line 103:
-- - -3 = 2x diminished
-- - -3 = 2x diminished
-- TODO: part of a rewrite for the mos degrees function
-- TODO: part of a rewrite for the mos degrees function
function p.mos_degreechain(input_mos, genchain_length_per_period, going_up)
function p.mos_degree(input_mos, genchain_length_per_period, going_up)
-- Default parameters for testing
-- Default parameters for testing
--[[
--[[
Line 137: Line 177:
end
end
-- Function that produces a list of mosdegrees for a mos with a step ratio
-- How far these extend is dependent on UDP
-- TODO: separate this into a helper module called "MOS notation"
function p.mos_degrees(input_mos, udp, step_ratio)
local input_mos = input_mos or mos.new(6, 2, 2)
local step_ratio = step_ratio or { 2, 1 }
-- Number of mossteps per period and equave, and number of periods
local mossteps_per_equave = input_mos.nL + input_mos.ns
local periods_per_equave = rat.gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- The default UDP corresponds to the brightest mode
-- If it's 5L 2s, default to the second-brightest mode
local udp_default = { mossteps_per_equave - periods_per_equave, 0 }
if scale_sig == "5L 2s" then
udp_default = { 5, 1 }
end
local udp_parsed = udp or udp_default
local generators_up = udp_parsed[1]
local generators_down = udp_parsed[2]
-- How long is the inital genchain for notes without accidentals?
local gens_up_per_period = generators_up / periods_per_equave
local gens_down_per_period = generators_down / periods_per_equave
-- Get and simplify the step ratio
local step_ratio_simplified = p.simplify_step_ratio(step_ratio)
local num = step_ratio_simplified[1]
local den = step_ratio_simplified[2]
-- Calculate genchain extend
local x = input_mos.nL / periods_per_equave
local y = input_mos.ns / periods_per_equave
local genchain_extend = 0
if num / den == 2 then
genchain_extend = x
elseif num == den or den == 0 then
genchain_extend = 0
else
genchain_extend = x * math.floor(num/2) + y * math.floor(den/2)
end
-- Arguments for the genchain function
local gens_up_per_period = generators_up / periods_per_equave
local gens_dn_per_period = generators_down / periods_per_equave
local asc_length = gens_up_per_period + genchain_extend
local des_length = gens_dn_per_period + genchain_extend
-- For ease of programming, the genchain function is called.
-- The ascending genchain represents intervals in their large size, followed by
-- augmented intervals, and the descending genchain represents intervals in their
-- small size, followed by diminished intervals.
-- Note: the descending genchain is actually a shorter ascending genchain, but the
-- equave complements are used instead.
local asc_genchain = mosg.mos_genchain(input_mos, mossteps_per_period - 2, asc_length, true)
local des_genchain = mosg.mos_genchain(input_mos, mossteps_per_period - 2, des_length, true)
-- How many esteps are in the equave? Per period? Per generator
local esteps_per_equave = input_mos.nL * num + input_mos.ns * den
local esteps_per_period = esteps_per_equave / periods_per_equave
-- How many esteps are the bright and dark generators?
local bright_gen = mos.bright_gen(input_mos)
local esteps_per_bright_gen = bright_gen['L'] * num + bright_gen['s'] * den
local esteps_per_dark_gen = esteps_per_period - esteps_per_bright_gen
-- Create an array of mosdegrees
local degrees = {}
for i = 1, esteps_per_equave + 1 do
table.insert(degrees, "")
end
-- Use the mos's prefix, if any; otherwise, use "mos"
local prefix = "mos"
-- Interpret the ascending genchain as mosdegrees
for j = 1, periods_per_equave do
local accumulator = 0
for i = 1, #asc_genchain[j] do
local index = (accumulator % esteps_per_period) + (j - 1) * esteps_per_period + 1
-- Convert the notationally agnostic form into a mosdegree
local note = asc_genchain[j][i]
local mosstep = note['mossteps']
local chroma_count = note['chromas']
-- What does it mean in text?
-- 0 chromas is major, 1 is augmented, 2 is doubly-augmented, etc
-- The first two elements in the genchain (the root and bright gen)
-- are both perfect (one size for root and large size for gen)
local quality = ""
if i == 1 then
-- The root is always perfect
quality = "Perf."
elseif i == 2 then
-- Use "perfect" if the mos is not nL ns; otherwise, use major
quality = "Perf."
else
if chroma_count == 0 then
quality = "Maj."
elseif chroma_count == 1 then
quality = "Aug."
else
quality = "Aug<sup>" .. chroma_count .. "</sup>"
end
end
-- Assemble the mosdegree by appending a quality to a k-mosdegree
-- If the mosstep is 0, use "unison" instead of "0-mosstep"
local degree_name = ""
if mosstep == 0 then
degree_name = quality .. " " .. "unison"
else
degree_name = quality .. " " .. mosstep .. "-" .. prefix .. "degree"
end
-- Add to degrees
degrees[index] = degrees[index] .. degree_name
accumulator = accumulator + esteps_per_bright_gen
end
end
-- Find the equave complements for the second genchain, and interpret those
-- as mosdegrees. This must come after the ascending genchains so in the case that
-- one pitch is reached in two directions (eg, C# and Db in standard tuning),
-- the one from the ascending chain (the C# in the example) comes first.
for j = 1, periods_per_equave do
local accumulator = 0
-- The start index for the following for loop starts at a note one period up
-- and goes down. However, with a multi-period mos, that same note is the root
-- for the next period, leading to a duplicate entry. To keep that from happening,
-- only start the for loop at 1 if this is the last period.
local start_index = 1
if j ~= periods_per_equave then
start_index = start_index + 1
end
for i = start_index, #des_genchain[j] do
local index = (accumulator % esteps_per_period) + (j - 1) * esteps_per_period + 1
-- Find the corresponding index for the equave complement
index = esteps_per_equave - index + 2
-- Convert the notationally agnostic form into a mosdegree
local note = des_genchain[j][i]
local mosstep = note['mossteps']
local chroma_count = note['chromas']
-- Use the equave complement of the mosstep
mosstep = mossteps_per_equave - mosstep
-- What does it mean in text? Use the rules for the ascending chain
-- but reversed in the other direction.
-- 0 chromas is minor, 1 is diminished, 2 is doubly-diminished, etc
-- The first two elements in the genchain (the period and dark gen)
-- are both perfect (one size for period and small size for gen).
local quality = ""
if i == 1 then
-- The period is always perfect
quality = "Perf."
elseif i == 2 then
-- Use "perfect" if the mos is not nL ns; otherwise, use minor
quality = "Perf."
else
if chroma_count == 0 or chroma_count == -0 then
quality = "Min."
elseif chroma_count == 1 then
quality = "Dim."
else
quality = "Dim<sup>" .. chroma_count .. "</sup>"
end
end
-- Assemble the mosdegree by appending a quality to a k-mosdegree
-- If the mosstep is the equave, use "equave" instead of "n-mosstep"
-- But if the equave is the octave, use "octave" instead
local degree_name = ""
if mosstep == mossteps_per_equave then
degree_name = quality .. " " .. "equave"
else
degree_name = quality .. " " .. mosstep .. "-" .. prefix .. "degree"
end
-- Add to degrees
if degrees[index] == "" then
degrees[index] = degrees[index] .. degree_name
else
degrees[index] = degrees[index] .. ", " .. degree_name
end
-- Increment by bright gen b/c this genchain is the ascending genchain's equave complements
accumulator = accumulator + esteps_per_bright_gen
end
end
return degrees
end


-- Algorithm:
-- Algorithm:
Line 405: Line 244:
end
end
-- Get the gamut
-- How long are the initial genchain lengths? (These correspond to the UDP)
local gamut = mosg.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol)
local gens_up_per_period = udp[1] / periods_per_equave
 
local gens_dn_per_period = udp[2] / periods_per_equave
-- Get the scale degrees
local degrees = p.mos_degrees(input_mos, udp, step_ratio)
-- How long should the genchains be?
-- The length should be such that:
-- - Every non-root interval is shown in its small, large, augmented, and
--  diminished size.
-- - The root and equave are shown in their perfect sizes, followed by their
--  augmented and diminished sizes respectively.
-- - Any non-root non-equave periods are shown in their perfect, augmented,
--  and diminished sizes.
-- To do this requires going up 2x+2y generators, and down the same amount.
-- Going up x+y gens from the root reaches every scale degree's large size,
-- plus the augmented root, then going up x+y-1 more gens reaches each
-- augmented degree. Same is true for going down to get minor/dim degrees.
local asc_chain_length = (input_mos.nL + input_mos.ns) * 2
local des_chain_length = (input_mos.nL + input_mos.ns) * 2
-- Get the genchains
local asc_genchain = mosg.mos_genchain(input_mos, gens_up_per_period, asc_chain_length, true)
local des_genchain = mosg.mos_genchain(input_mos, gens_dn_per_period, des_chain_length, false)
-- Get the degrees
local asc_degrees = p.mos_degrees(input_mos, asc_chain_length, true)
local des_degrees = p_mos_degrees(input_mos, des_chain_length, true)
-- Format the output as a table, starting with the header row
-- Format the output as a table, starting with the header row
Line 417: Line 277:
local steps_in_et = input_mos.nL * step_ratio[1] + input_mos.ns * step_ratio[2]
local steps_in_et = input_mos.nL * step_ratio[1] + input_mos.ns * step_ratio[2]
local et_for_mos = et.new(steps_in_et, input_mos.equave)
local et_for_mos = et.new(steps_in_et, input_mos.equave)
result = result .. "! Steps of " .. et.as_string(et_for_mos) .. " !! Cent value !! Scale degree !! Note name on ".. string.sub(note_symbols, 1, 1) .. "\n"
result = result .. "! Scale degree !! Steps !! Cents !! Note name on ".. string.sub(note_symbols, 1, 1) .. "\n"
-- How many esteps per period? Per bright/dark gen?
local esteps_per_period = steps_in_et / periods_per_equave
local bright_gen = mos.bright_gen(input_mos)
local esteps_per_bright_gen = bright_gen['L'] * step_ratio[1] + bright_gen['s'] * step_ratio[2]
local esteps_per_dark_gen = esteps_per_period - esteps_per_bright_gen
-- Add the rows
-- Add the rows
Line 423: Line 289:
local cents_per_equave = rat.cents(input_mos.equave) -- Equave in cents
local cents_per_equave = rat.cents(input_mos.equave) -- Equave in cents
for i = 1, #gamut do
-- For each period, add a row containing a scale degree, step count, cent
-- Get the note name
-- value, and note name from the ascending genchain, then do the same with
-- If the note name has a slash, replace it with a newline
-- the descending genchain, in reverse and skipping the perfect root and
local note_name = gamut[i]
-- raising any other root by one period. Repeat for all other periods.
note_name = note_name:gsub("/", "\n")
-- For the last period, add the perfect root as the perfect equave.
for i = 1, #periods_per_equave do
-- Add degrees from ascending chain
for j = 1, #asc_genchain[i] do
local note = asc_genchain[i][j]
local mossteps = note['mossteps']
local chromas = note['chromas']
local quality = asc_degrees['quality']
-- Find the note name
local note_name = p.mosstep_and_chroma_to_note_name(mossteps, chromas, string.sub(note_symbols, mossteps, mossteps), chroma_plus_symbol)
-- Find the degree name
-- If the degree is the 0-mosdegree, say it's the unison instead
local degree_name = p.mosstep_and_quality_to_degree(mossteps, quality)
degree_name = string:gsub("0-mosstep", "unison")
-- Find the estep count
local estep_count = ((j - 1) * esteps_per_bright_gen) % esteps_per_period + (j - 1) * esteps_per_period
-- Find the cent value
local cent_value = et.cents(et_for_mos, estep_count)
-- Add the row
result = result .. "|- " .. degree_name .. " || " .. estep_count .. " || " .. cent_value .. " || " .. note_name .. "\n"
end
-- Get the scale degree
-- Calculate the stop value for the for loop as being 1 or 2, depending
-- If there's a comma, replace it with a newline
-- on whether this is the last period or not
local degree = degrees[i]
local stop_value = 1
degree = degree:gsub(", ", "\n")
if i == periods_per_equave then
stop_value = stop_value + 1
end
-- Does the current estep correspond to a natural of the mos?
-- Add degrees from descending chain
-- In other words, are there no accidentals? If so, color
for j = #des_genchain[i], stop_value, -1 do
-- the row white. Otherwise, color it black. This is to mimic a piano
local note = des_genchain[i][j]
-- layout.
local mossteps = note['mossteps']
local has_accidentals = string.find(note_name, chroma_plus_symbol) or string.find(note_name, chroma_minus_symbol)
local chromas = note['chromas']
local quality = asc_degrees['quality']
-- Add row
if has_accidentals then
-- Find the note name
result = result .. "|-\n"
-- If the mosstep is the root of the period, add a period to it
result = result .. '|bgcolor="#c8ccd1"|' .. (i - 1) * step_ratio_gcd .. "\n"
if mossteps % mossteps_per_period == 0 then
result = result .. '|bgcolor="#c8ccd1"|' .. utils._round_dec(cents_per_equave * (i - 1) / (#gamut - 1), 3) .. "¢\n"
mossteps = mossteps + mossteps_per_period
result = result .. '|bgcolor="#c8ccd1"|' .. degree .. "\n"
end
result = result .. '|bgcolor="#c8ccd1"|' .. note_name .. "\n"
local note_name = p.mosstep_and_chroma_to_note_name(mossteps, chromas, string.sub(note_symbols, mossteps, mossteps), chroma_plus_symbol)
else
result = result .. "|-\n"
-- Find the degree name
result = result .. "| " .. (i - 1) * step_ratio_gcd .. "\n"
-- If the degree corresponds to the equave, say it's the equave
result = result .. "| " .. utils._round_dec(cents_per_equave * (i - 1) / (#gamut - 1), 3) .. "¢\n"
local degree_name = p.mosstep_and_quality_to_degree(mossteps, quality)
result = result .. "| " .. degree .. "\n"
local equave_degree_name = (input_mos.nL + input_mos.ns) .. "-mosstep"
result = result .. "| " .. note_name .. "\n"
degree_name = string:gsub(equave_degree_name, "equave")
-- Find the estep count
local estep_count = ((j - 1) * esteps_per_dark_gen) % esteps_per_period + (j - 1) * esteps_per_period
-- Find the cent value
local cent_value = et.cents(et_for_mos, estep_count)
-- Add the row
result = result .. "|- " .. degree_name .. " || " .. estep_count .. " || " .. cent_value .. " || " .. note_name .. "\n"
end
end
end
end

Revision as of 07:35, 22 June 2023

Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:MOS degrees.
Module:MOS degrees is deprecated and has been replaced by Module:MOS tunings. Further use of this module is not advised. This module is kept for historical purposes and should not be deleted.
Introspection summary for Module:MOS degrees 
Functions provided (7)
Line Function Params
11 parse_step_ratio (step_ratio_unparsed)
25 parse_udp (step_ratio_unparsed)
40 mosstep_and_chroma_to_note_name (mossteps, chromas, note_symbol, chroma_symbol)
56 mosstep_and_quality_to_degree (mossteps, quality)
79 simplify_step_ratio (step_ratio_unsimplified)
105 mos_degree (input_mos, genchain_length_per_period, going_up)
184 mos_degrees_frame (invokable) (frame)
Lua modules required (5)
Variable Module Functions used
et Module:ET new
cents
mos Module:MOS bright_gen
parse
new
as_string
mosg Module:MOS gamut mos_genchain
rat Module:Rational gcd
cents
utils Module:Utils dependency not used

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


local mos = require('Module:MOS')
local mosg = require('Module:MOS gamut')
local et = require('Module:ET')
local rat = require('Module:Rational')
local utils = require('Module:Utils')
--local mosnot = require('Module:MOS notation')
local p = {}

-- Helper function for parsing a step ratio entered as a string "p/q"
-- TODO: separate this into a helper module called "MOS notation"
function p.parse_step_ratio(step_ratio_unparsed)
	
	local parsed = {}
	for entry in string.gmatch(step_ratio_unparsed, '([^/]+)') do
		local trimmed = entry:gsub("^%s*(.-)%s*$", "%1")
		table.insert(parsed, trimmed)		-- Add to array
	end
	
	local ratio = { tonumber(parsed[1]), tonumber(parsed[2]) }
	return ratio
end

-- Helper function for parsing a UDP entered as a string "up,dp"
-- To avoid potential issues, the "," character is used instead of "|"
function p.parse_udp(step_ratio_unparsed)
	
	local parsed = {}
	for entry in string.gmatch(step_ratio_unparsed, '([^,]+)') do
		local trimmed = entry:gsub("^%s*(.-)%s*$", "%1")
		table.insert(parsed, trimmed)		-- Add to array
	end
	
	local udp = { tonumber(parsed[1]), tonumber(parsed[2]) }
	return udp
end

-- Helper function that converts a note name given as a quantity of mossteps
-- and chromas (see gamut function) into a name, such as "C#"
-- To be used in conjunction with the genchain function
function p.mosstep_and_chroma_to_note_name(mossteps, chromas, note_symbol, chroma_symbol)
	local note_name = note_symbol
	if note['chromas'] < 0 then
		note_name = note_name .. string.rep(chroma_symbol, chromas)
	elseif note['chromas'] > 0 then
		note_name = note_name .. string.rep(chroma_symbol, -1 * chromas)
	end
	return note_name
end

-- Helper function that converts a scale degree given as a quantity of mossteps
-- and a numeric quality (0=perf, 1=maj, -1=min, 2=aug, -2=dim, etc) into a
-- scale degree
-- To be used in conjunction with the degrees function
-- TODO: add ability to change naming from k-mosstep to mos-(k+1)th, since
-- there are cases where that's favored instead of tamnams
function p.mosstep_and_quality_to_degree(mossteps, quality)
	local degree_name = mossteps .. "-mosstep"
	
	if quality == 1 then
		degree_name = "Major " .. degree_name
	elseif quality == 2 then
		degree_name = "Augmented " .. degree_name
	elseif quality > 2 then
		degree_name = (quality - 1) .. "× augmented " .. degree_name
	elseif quality == -1 then
		degree_name = "Minor " .. degree_name
	elseif quality == -2 then
		degree_name = "Diminished " .. degree_name
	elseif quality < -2 then
		degree_name = (math.abs(quality) - 1) .. "× diminished" .. degree_name
	end
	
	return degree_name
	
end

-- Helper function to simplify step ratio
-- TODO: separate this into a helper module called "MOS notation"
function p.simplify_step_ratio(step_ratio_unsimplified)
	
	-- Get and simplify the step ratio
	local kp = step_ratio_unsimplified[1]
	local kq = step_ratio_unsimplified[2]
	local k = rat.gcd(kp, kq)
	local num = kp / k
	local den = kq / k
	
	return { num, den }
end

-- Function that produces a chain of scale degrees. What scale degrees are
-- reached by stacking a generator?
-- (EG, major 2nd, augmented 2nd, etc)
-- This function only works one direction at a time, so it's necessary to call
-- it twice, one for each direction.
-- Quality encodes maj/min/aug/perf/dim numerically:
-- -  3 = 2x augmented
-- -  2 = 1x augmented
-- -  1 = major
-- -  0 = perfect (used for generators and root)
-- - -1 = minor
-- - -2 = 1x diminished
-- - -3 = 2x diminished
-- TODO: part of a rewrite for the mos degrees function
function p.mos_degree(input_mos, genchain_length_per_period, going_up)
	-- Default parameters for testing
	--[[
	local input_mos = input_mos or mos.new(5, 2, 2)
	local genchain_length_per_period = genchain_length_per_period or 10
	local going_up = true
	]]--
	
	-- Get the number of mossteps per period and equave
	local mossteps_per_equave = input_mos.nL + input_mos.ns
	local periods_per_equave = rat.gcd(input_mos.nL, input_mos.ns)
	local mossteps_per_period = mossteps_per_equave / periods_per_equave
	
	-- Get the number of mossteps for the generators
	local bright_gen = mos.bright_gen(input_mos)
	local mossteps_per_bright_gen = bright_gen['L'] + bright_gen['s']
	local mossteps_per_dark_gen = mossteps_per_period - mossteps_per_bright_gen
	
	local degreechain = {}
	for j = 1, periods_per_equave do
		local chain_for_period = {}

		for i = 1, genchain_length_per_period do
			
			-- Calculate mossteps
			local mossteps = 0
			if going_up then
				mossteps = (i - 1) * mossteps_per_bright_gen % mossteps_per_period + (j - 1) * mossteps_per_period
			else
				mossteps = (i - 1) * mossteps_per_dark_gen % mossteps_per_period + (j - 1) * mossteps_per_period
			end

			-- Calculate quality
			-- The first two elements in the chain are always perfect
			-- All intervals after that are major (or minor if going down)
			-- After the major intervals are augmented intervals, which starts
			-- with the augmented dark generator, which comes before the
			-- augmented unison. (or minor and dim bright gen if going down)
			-- For nL ns mosses, generators are major and minor instead, so only
			-- the root is perfect
			local quality = 0
			if input_mos.nL ~= input_mos.ns then
				if i == 1 or i == 2 then
					quality = 0
				else
					-- Offsetting i by +1 will make it so the dark generator
					-- before the augmented unison is denoted as augmented,
					-- but lua's start-from-1 indexing offsets it by 1 already.
					quality = math.floor(i / mossteps_per_period) + 1
					if not going_up then
						quality = quality * -1
					end
				end
			else
				if i == 1 then
					quality = 0
				else
					quality = math.floor((i + 1) / mossteps_per_period)
					if not going_up then
						quality = quality * -1
					end
				end
			end
			
			-- Put together the name
			local degree = { ['mossteps'] = mossteps, ['quality'] = quality }
			table.insert(chain_for_period, degree)
		end
		table.insert(degreechain, chain_for_period)
	end
	
	return degreechain
	
end

-- Algorithm:
-- Use the input mos, udp, and step ratio to find the genchains
-- Using the genchains and UDP, find the mos's intervals/degrees
-- Format the result as a table
function p.mos_degrees_frame(frame)
	-- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio)
	local input_mos_unparsed = frame.args['Scale Signature']
	local input_mos = mos.parse(input_mos_unparsed) or mos.new(2, 5, 2)
	
	-- Step ratio
	local step_ratio = { 2, 1 }
	if string.len(frame.args['Step Ratio']) > 0 then
		step_ratio = p.parse_step_ratio(frame.args['Step Ratio'])
	end
	
	-- Get the number of mossteps per period and equave
	local mossteps_per_equave = input_mos.nL + input_mos.ns
	local periods_per_equave = rat.gcd(input_mos.nL, input_mos.ns)
	local mossteps_per_period = mossteps_per_equave / periods_per_equave
	
	-- If certain params were left blank and the scalesig is 5L 2s, the default
	-- params will be for standard notation
	local scale_sig = mos.as_string(input_mos)
	
	-- The default UDP corresponds to the brightest mode
	-- If it's 5L 2s, default to the second-brightest mode
	local udp = { mossteps_per_equave - periods_per_equave, 0 }
	if scale_sig == "5L 2s" then
		udp = { 5, 1 }
	end
	if string.len(frame.args['UDP']) > 0 then
		udp = p.parse_udp(frame.args['UDP'])
	end
	
	-- Get note symbols
	-- If this param was blank, default to diamond-mos; limited to 17 note names
	-- But if it's blank and the scalesig is 5L 2s, default to standard notation
	-- This order of operations allows for overriding standard notation for 5L 2s
	local note_symbols_main = "JKLMNOPQRSTUVWXYZ"
	local note_symbols = string.sub(note_symbols_main, 1, mossteps_per_equave)
	if scale_sig == "5L 2s" then
		note_symbols = "CDEFGAB"
	end
	-- If a value was entered, override the default value
	if string.len(frame.args['Note Symbols']) > 0 then
		note_symbols = frame.args['Note Symbols']
	end
	
	-- Get accidental symbols
	-- If this param was blank, default to diamond-mos symbols & and @
	-- unless the mos is 5L 2s, then it's sharp and flat # and b
	-- This order of operations allows for overriding standard notation for 5L 2s
	local chroma_plus_symbol = "&"
	local chroma_minus_symbol = "@"
	if scale_sig == "5L 2s" then
		chroma_plus_symbol = "#"
		chroma_minus_symbol = "b"
	end
	-- If value(s) were entered, override the default values
	if string.len(frame.args['Sharp Symbol']) > 0 then
		chroma_plus_symbol = frame.args['Sharp Symbol']
	end
	if string.len(frame.args['Flat Symbol']) > 0 then
		chroma_minus_symbol = frame.args['Flat Symbol']
	end
	
	-- How long are the initial genchain lengths? (These correspond to the UDP)
	local gens_up_per_period = udp[1] / periods_per_equave
	local gens_dn_per_period = udp[2] / periods_per_equave
	
	-- How long should the genchains be?
	-- The length should be such that:
	-- - Every non-root interval is shown in its small, large, augmented, and
	--   diminished size.
	-- - The root and equave are shown in their perfect sizes, followed by their
	--   augmented and diminished sizes respectively.
	-- - Any non-root non-equave periods are shown in their perfect, augmented,
	--   and diminished sizes.
	-- To do this requires going up 2x+2y generators, and down the same amount.
	-- Going up x+y gens from the root reaches every scale degree's large size,
	-- plus the augmented root, then going up x+y-1 more gens reaches each
	-- augmented degree. Same is true for going down to get minor/dim degrees.
	local asc_chain_length = (input_mos.nL + input_mos.ns) * 2
	local des_chain_length = (input_mos.nL + input_mos.ns) * 2
	
	-- Get the genchains
	local asc_genchain = mosg.mos_genchain(input_mos, gens_up_per_period, asc_chain_length, true)
	local des_genchain = mosg.mos_genchain(input_mos, gens_dn_per_period, des_chain_length, false)
	
	-- Get the degrees
	local asc_degrees = p.mos_degrees(input_mos, asc_chain_length, true)
	local des_degrees = p_mos_degrees(input_mos, des_chain_length, true)
	
	-- Format the output as a table, starting with the header row
	local result = '{| class="wikitable"\n'

	-- Produce the headers
	local steps_in_et = input_mos.nL * step_ratio[1] + input_mos.ns * step_ratio[2]
	local et_for_mos = et.new(steps_in_et, input_mos.equave)
	result = result .. "! Scale degree !! Steps !! Cents !! Note name on ".. string.sub(note_symbols, 1, 1) .. "\n"
	
	-- How many esteps per period? Per bright/dark gen?
	local esteps_per_period = steps_in_et / periods_per_equave
	local bright_gen = mos.bright_gen(input_mos)
	local esteps_per_bright_gen = bright_gen['L'] * step_ratio[1] + bright_gen['s'] * step_ratio[2]
	local esteps_per_dark_gen = esteps_per_period - esteps_per_bright_gen
	
	-- Add the rows
	local step_ratio_gcd = rat.gcd(step_ratio[1], step_ratio[2])		-- GCD of the sizes of L and s, in case L:s isn't simplified
	local cents_per_equave = rat.cents(input_mos.equave)				-- Equave in cents
	
	-- For each period, add a row containing a scale degree, step count, cent
	-- value, and note name from the ascending genchain, then do the same with
	-- the descending genchain, in reverse and skipping the perfect root and
	-- raising any other root by one period. Repeat for all other periods.
	-- For the last period, add the perfect root as the perfect equave.
	for i = 1, #periods_per_equave do
		-- Add degrees from ascending chain
		for j = 1, #asc_genchain[i] do
			local note = asc_genchain[i][j]
			local mossteps = note['mossteps']
			local chromas = note['chromas']
			local quality = asc_degrees['quality']
			
			-- Find the note name
			local note_name = p.mosstep_and_chroma_to_note_name(mossteps, chromas, string.sub(note_symbols, mossteps, mossteps), chroma_plus_symbol)
			
			-- Find the degree name
			-- If the degree is the 0-mosdegree, say it's the unison instead
			local degree_name = p.mosstep_and_quality_to_degree(mossteps, quality)
			degree_name = string:gsub("0-mosstep", "unison")
			
			-- Find the estep count
			local estep_count = ((j - 1) * esteps_per_bright_gen) % esteps_per_period + (j - 1) * esteps_per_period
			
			-- Find the cent value
			local cent_value = et.cents(et_for_mos, estep_count)
			
			-- Add the row
			result = result .. "|- " .. degree_name .. " || " .. estep_count .. " || " .. cent_value .. " || " .. note_name .. "\n"
		end
		
		-- Calculate the stop value for the for loop as being 1 or 2, depending
		-- on whether this is the last period or not
		local stop_value = 1
		if i == periods_per_equave then
			stop_value = stop_value + 1
		end
		
		-- Add degrees from descending chain
		for j = #des_genchain[i], stop_value, -1 do
			local note = des_genchain[i][j]
			local mossteps = note['mossteps']
			local chromas = note['chromas']
			local quality = asc_degrees['quality']
			
			-- Find the note name
			-- If the mosstep is the root of the period, add a period to it
			if mossteps % mossteps_per_period == 0 then
				mossteps = mossteps + mossteps_per_period
			end
			local note_name = p.mosstep_and_chroma_to_note_name(mossteps, chromas, string.sub(note_symbols, mossteps, mossteps), chroma_plus_symbol)
			
			-- Find the degree name
			-- If the degree corresponds to the equave, say it's the equave
			local degree_name = p.mosstep_and_quality_to_degree(mossteps, quality)
			local equave_degree_name = (input_mos.nL + input_mos.ns) .. "-mosstep"
			degree_name = string:gsub(equave_degree_name, "equave")
			
			-- Find the estep count
			local estep_count = ((j - 1) * esteps_per_dark_gen) % esteps_per_period + (j - 1) * esteps_per_period
			
			-- Find the cent value
			local cent_value = et.cents(et_for_mos, estep_count)
			
			-- Add the row
			result = result .. "|- " .. degree_name .. " || " .. estep_count .. " || " .. cent_value .. " || " .. note_name .. "\n"
		end
	end
	
	result = result .. "|}"
	
	return result
end
	
return p