Module:MOS intro: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
m Minor rewording to meet current (personal) coding standards
Ganaram inukshuk (talk | contribs)
Broke up the function that generates the intro into several helper functions to make it easier to add additional info
Line 14: Line 14:
end
end


-- Function that creates a mos intro sentence, given a mos and any other names
-- Helper function
function p.mos_intro(input_mos, other_names)
-- Lists out names, with each name being bold
local input_mos = input_mos or mos.new(5, 2)
function p.mos_intro_list_names(mos_names, conjunction)
local other_names = other_names or "name1; name2; name3"
local mos_names = mos_names or { "name1", "name2", "name3" }
local conjunction = conjunction or "and"
-- Get the step counts and number of periods
-- List the names
local nL = input_mos.nL -- Number of large steps per equave
local names_list = ""
local ns = input_mos.ns -- Number of small steps per equave
if #mos_names == 1 then
local n = utils._gcd(nL, ns) -- Number of periods
-- Only one mos name
local x = round(nL / n) -- Number of large steps per period
names_list = string.format("'''%s'''", mos_names[1])
local y = round(ns / n) -- Number of small steps per period
elseif #mos_names == 2 then
-- Two mos names (name and alternate-name)
names_list = string.format("'''%s''' %s '''%s'''", mos_names[1], conjunction, mos_names[2])
elseif #mos_names > 2 then
-- Three or more mos names
for i = 1, #mos_names - 1 do
names_list = names_list .. string.format("'''%s''', ", mos_names[i])
end
names_list = names_list .. string.format("%s '''%s'''", conjunction, mos_names[#mos_names])
else
-- No names
names_list = ""
end
-- Get the equave as a ratio and in cents
return names_list
local equave = input_mos.equave
end
local equave_in_cents = rat.cents(equave)


-- Get the period in cents
-- Helper function
local period_in_cents = equave_in_cents / n
-- Introduces the mos by its scale sig and names
function p.mos_intro_scalesig_and_tamnams_names(input_mos, tamnams_name, other_names)
local input_mos = input_mos or mos.new(5, 2)
local tamnams_name = tamnams_name or "diatonic"
local other_names = other_names or "other-name"
-- Get the scalesig
-- Get the scalesig
Line 37: Line 53:
-- Get all the mos's names, starting with tamnams names if applicable
-- Get all the mos's names, starting with tamnams names if applicable
-- Some mosses have two tamnams names, so it's necessary to tokenize it
local tamnams_names_list = p.mos_intro_list_names(p.parse_entries(tamnams_name), "and")
local tamnams_name = mos.tamnams_name[scale_sig] or ""
local other_names_list = p.mos_intro_list_names(p.parse_entries(other_names), "or")
local mos_names = p.parse_entries(tamnams_name)
-- Construct the sentence
local sentence = string.format("'''%s''' ", scale_sig)
-- Tokenize the other names passed in, then add them to the mos names
-- Add names
local other_names_tokenized = p.parse_entries(other_names)
if tamnams_names_list ~= "" and if_others_names_list ~= "" then
for i = 1, #other_names_tokenized do
-- There are both tamnams names and alternate names
mos_names[#mos_names + 1] = other_names_tokenized[i]
sentence = sentence .. string.format("([[TAMNAMS]] name %s; also called %s)", tamnams_names_list, other_names_list)
elseif tamnams_names_list ~= "" and if_others_names_list == "" then
-- There are only tamnams names
sentence = sentence .. string.format("([[TAMNAMS]] name %s)", tamnams_names_list)
elseif tamnams_names_list == "" and if_others_names_list ~= "" then
-- There are no tamnams names but there are alternate names
sentence = sentence .. string.format("(also called %s)", other_names_list)
end
end
-- Get the eds (ets) corresponding to the collapsed and equalized mosses
return sentence
local collapsed_et = et.new(nL, input_mos.equave)
end
local equalized_et = et.new(nL + ns, input_mos.equave)
 
-- Helper function
-- Introduces the mos by its step counts and number of periods
-- This is meant to immediately follow the sentence provided by:
-- - mos_intro_tamnams_names()
function p.mos_intro_steps_and_periods(input_mos)
local input_mos = input_mos or mos.new(3, 6, 3)
-- Get the sizes of the generator for the collapsed and equalized et in steps
-- Get the equave and the equave in cents
-- These are used to calculate cent values for the generators
local equave = input_mos.equave
local generator = mos.bright_gen(input_mos)
local equave_in_cents = rat.cents(equave)
local gen_collapsed_in_steps = generator["L"]
local equave_as_ratio = rat.as_ratio(equave)
local gen_equalized_in_steps = generator["L"] + generator["s"]
-- Is the mos octave-equivalent or non-octave?
-- Get the step counts and number of periods
local is_octave_equivalent = equave_in_cents == 1200
local nL = input_mos.nL -- Number of large steps per equave
local ns = input_mos.ns -- Number of small steps per equave
local n = utils._gcd(nL, ns) -- Number of periods
local x = round(nL / n) -- Number of large steps per period
local y = round(ns / n) -- Number of small steps per period
-- How many places should cent values be rounded to?
-- Construct the sentence
local round = 3
sentence = " is "
-- Create the intro string starting with the scale
-- Is the scale nonoctave?
local intro = "'''" .. scale_sig .. "'''"
if equave_in_cents == 1200 then
sentence = sentence .. string.format("an [[octave-equivalent]] [[moment of symmetry]] scale")
-- Add the mos names, if any
if #mos_names == 1 then
-- Only one mos name
intro = intro .. ", also called '''" .. mos_names[1] .. "''', is a"
elseif #mos_names == 2 then
-- Two mos names (name and alternate-name)
intro = intro .. ", also called '''" .. mos_names[1] .. "''' or '''" .. mos_names[2] .. "''', is a"
elseif #mos_names > 2 then
-- Three or more mos names (name, alternate-name, and other-alternate-name)
intro = intro .. ", also called "
for i = 1, #mos_names - 1 do
intro = intro .. "'''" .. mos_names[i] .. "''', "
end
intro = intro .. "or '''" .. mos_names[#mos_names] .. "''', is a"
else
else
-- No names
sentence = sentence .. string.format("a [[non-octave]], [[%s]]-equivalent [[moment_of_symmetry]] scale", equave_as_ratio)
intro = intro .. " is a"
end
end
-- Add whether it's non-octave
-- What are the step counts? Should the word "step" contain an s?
if is_octave_equivalent then
local s_nl = ""
intro = intro .. " [[moment of symmetry]] scale consisting of "
local s_ns = ""
else
if nL ~= 1 then
intro = intro .. " [[non-octave]] [[moment of symmetry]] scale consisting of "
s_nl = "s"
end
if ns ~= 1 then
s_ns = "s"
end
end
sentence = sentence .. string.format(" containing %d large step%s and %d small step%s", nL, s_nl, ns, s_ns)
-- Add the step counts per period
-- How does it repeat?
-- Determine where "steps" should be plural or singular, as well
local s_x = ""
if nL == 1 then
local s_y = ""
intro = intro .. "1 large step and "
local repetition = ""
else
if x ~= 1 then
intro = intro .. nL .. " large steps and "
s_x = "s"
end
if y ~= 1 then
s_y = "s"
end
if n == 2 then
repetition = "twice"
elseif n > 2 then
repetition = string.format("%d times", n)
end
end
if ns == 1 then
if n == 1 and equave_in_cents == 1200 then
intro = intro .. "1 small step,"
sentence = sentence .. string.format(" and repeating every [[octave]].")
else
elseif n == 1 and equave_in_cents ~= 1200 then
intro = intro .. ns .. " small steps,"
sentence = sentence .. string.format(" and repeating every interval of %s (%.3f¢).", equave_as_ratio, equave_in_cents)
elseif n ~= 1 and equave_in_cents == 1200 then
sentence = sentence .. string.format(", with a [[period]] of %d large step%s and %d small step%s", x, s_x, y, s_y)
sentence = sentence .. string.format(" that repeats every %.3f¢, or %s every [[octave]].", equave_in_cents / n, repetition)
elseif n ~= 1 and equave_in_cents ~= 1200 then
sentence = sentence .. string.format(", with a [[period]] of %d large step%s and %d small step%s", x, s_x, y, s_y)
sentence = sentence .. string.format(" that repeats every %.3f¢, or %s every interval of %s (%.3f¢).", equave_in_cents / n, repetition, equave_as_ratio, equave_in_cents)
end
end
-- Add the number of repetitions
return sentence
-- If multi-period, determine whether "steps" should be plural or singular, as well
end
if n == 1 then
 
intro = intro .. " repeating every "
-- Helper function
else
-- Calculates the generator ranges of the mos
intro = intro .. " with a [[period]] of "
function p.mos_intro_generator_ranges(input_mos)
if x == 1 then
local input_mos = input_mos or mos.new(5, 2)
intro = intro .. "1 large step and "
else
-- Get the step counts and number of periods
intro = intro .. x .. " large steps and "
local nL = input_mos.nL -- Number of large steps per equave
end
local ns = input_mos.ns -- Number of small steps per equave
if y == 1 then
local n = utils._gcd(nL, ns) -- Number of periods
intro = intro .. "1 small step "
else
-- Get the equave as a ratio and in cents
intro = intro .. y .. " small steps "
local equave = input_mos.equave
end
local equave_in_cents = rat.cents(equave)
if n == 2 then
intro = intro .. "that repeats twice every "
-- Get the eds (ets) corresponding to the collapsed and equalized mosses
else
local collapsed_et = et.new(nL, input_mos.equave)
intro = intro .. "that repeats " .. n .. " times every "
local equalized_et = et.new(nL + ns, input_mos.equave)
end
end
-- Get the sizes of the bright generator for the collapsed and equalized et in steps
-- These are used to calculate cent values for the generators
-- The values for the dark generator are the period complements
local generator = mos.bright_gen(input_mos)
local gen_collapsed_in_steps = generator["L"]
local gen_equalized_in_steps = generator["L"] + generator["s"]
local bright_gen_max = et.cents(collapsed_et, gen_collapsed_in_steps)
local bright_gen_min = et.cents(equalized_et, gen_equalized_in_steps)
local dark_gen_min = equave_in_cents / n - bright_gen_max
local dark_gen_max = equave_in_cents / n - bright_gen_min
-- Add the equivalence interval
local sentence = string.format("[[Generating intervals|generator]] that produce this scale range from %.3f¢ to %.3f¢, or from %.3f¢ to %.3f¢.", bright_gen_min, bright_gen_max, dark_gen_min, dark_gen_max)
if is_octave_equivalent then
intro = intro .. "[[octave]]"
else
intro = intro .. "interval of [[" .. rat.as_ratio(equave) .. "]] (" .. utils._round_dec(equave_in_cents, round) .. "¢)"
end
-- Add the period (this is a pun)
return sentence
if n == 1 then
end
intro = intro .. ". "
 
else
-- Function that creates a mos intro, given a mos and any other names
intro = intro .. ", or every " .. utils._round_dec(period_in_cents, round) .. "¢. "
-- Intro (or lead section) consists of multiple sentences, and each is called individually
end
function p.mos_intro(input_mos, other_names)
local input_mos = input_mos or mos.new(5, 2)
local other_names = other_names or "name1; name2; name3"
-- Add the generator range (for bright generator)
-- Get tamnams names
local collapsed_gen_in_cents = et.cents(collapsed_et, gen_collapsed_in_steps)
local tamnams_name = mos.tamnams_name[scale_sig] or ""
local equalized_gen_in_cents = et.cents(equalized_et, gen_equalized_in_steps)
intro = intro .. "This scale is made using a [[generator]] ranging from " .. utils._round_dec(equalized_gen_in_cents, round)
intro = intro .. "¢ to " .. utils._round_dec(collapsed_gen_in_cents, round) .. "¢, "
-- Add the generator range (for dark generator)
-- Construct the sentence
-- The dark generator range is the period complement of the bright generator's extremes,
local scalesig_and_names = p.mos_intro_scalesig_and_tamnams_names(input_mos, tamnams_name, other_names)
-- or the period in cents (equave / periods) minus the size of the bright generator
local steps_and_periods = p.mos_intro_steps_and_periods(input_mos)
intro = intro .. "or from " .. utils._round_dec(equave_in_cents / n - collapsed_gen_in_cents, round)
local gens = p.mos_intro_generator_ranges(input_mos)
intro = intro .. "¢ to " .. utils._round_dec(equave_in_cents / n - equalized_gen_in_cents, round) .. "¢."
return intro
return string.format("%s %s %s", scalesig_and_names, steps_and_periods, gens)
end
end



Revision as of 10:52, 24 November 2023

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

This module automatically fills in an introduction for MOS scales. It clarifies the equave, numbers of long and short steps, and range of generators that produce it.

Introspection summary for Module:MOS intro 
Functions provided (7)
Line Function Params
8 parse_entries (unparsed)
18 mos_intro_list_names (mos_names, conjunction)
46 mos_intro_scalesig_and_tamnams_names (input_mos, tamnams_name, other_names)
80 mos_intro_steps_and_periods (input_mos)
148 mos_intro_generator_ranges (input_mos)
183 mos_intro (input_mos, other_names)
199 mos_intro_frame (invokable) (frame)
Lua modules required (4)
Variable Module Functions used
et Module:ET new
cents
mos Module:MOS new
as_string
bright_gen
parse
rat Module:Rational cents
as_ratio
utils Module:Utils _gcd

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


local mos = require('Module:MOS')
local rat = require('Module:Rational')
local utils = require('Module:Utils')
local et = require('Module:ET')
local p = {}

-- Helper function that parses entries from a semicolon-delimited string and returns them in an array
function p.parse_entries(unparsed)
	local parsed = {}
	for entry in string.gmatch(unparsed, '([^;]+)') do
		table.insert(parsed, entry)		-- Add to array
	end
	return parsed
end

-- Helper function
-- Lists out names, with each name being bold
function p.mos_intro_list_names(mos_names, conjunction)
	local mos_names = mos_names or { "name1", "name2", "name3" }
	local conjunction = conjunction or "and"
	
	-- List the names
	local names_list = ""
	if #mos_names == 1 then
		-- Only one mos name
		names_list = string.format("'''%s'''", mos_names[1])
	elseif #mos_names == 2 then
		-- Two mos names (name and alternate-name)
		names_list = string.format("'''%s''' %s '''%s'''", mos_names[1], conjunction, mos_names[2])
	elseif #mos_names > 2 then
		-- Three or more mos names
		for i = 1, #mos_names - 1 do
			names_list = names_list .. string.format("'''%s''', ", mos_names[i])
		end
		names_list = names_list .. string.format("%s '''%s'''", conjunction, mos_names[#mos_names])
	else
		-- No names
		names_list = ""
	end
	
	return names_list
end

-- Helper function
-- Introduces the mos by its scale sig and names
function p.mos_intro_scalesig_and_tamnams_names(input_mos, tamnams_name, other_names)
	local input_mos = input_mos or mos.new(5, 2)
	local tamnams_name = tamnams_name or "diatonic"
	local other_names = other_names or "other-name"
	
	-- Get the scalesig
	local scale_sig = mos.as_string(input_mos)
	
	-- Get all the mos's names, starting with tamnams names if applicable
	local tamnams_names_list = p.mos_intro_list_names(p.parse_entries(tamnams_name), "and")
	local other_names_list = p.mos_intro_list_names(p.parse_entries(other_names), "or")
	
	-- Construct the sentence
	local sentence = string.format("'''%s''' ", scale_sig)
	
	-- Add names
	if tamnams_names_list ~= "" and if_others_names_list ~= "" then
		-- There are both tamnams names and alternate names
		sentence = sentence .. string.format("([[TAMNAMS]] name %s; also called %s)", tamnams_names_list, other_names_list)
	elseif tamnams_names_list ~= "" and if_others_names_list == "" then
		-- There are only tamnams names
		sentence = sentence .. string.format("([[TAMNAMS]] name %s)", tamnams_names_list)
	elseif tamnams_names_list == "" and if_others_names_list ~= "" then
		-- There are no tamnams names but there are alternate names
		sentence = sentence .. string.format("(also called %s)", other_names_list)
	end
	
	return sentence
end

-- Helper function
-- Introduces the mos by its step counts and number of periods
-- This is meant to immediately follow the sentence provided by:
-- - mos_intro_tamnams_names()
function p.mos_intro_steps_and_periods(input_mos)
	local input_mos = input_mos or mos.new(3, 6, 3)
	
	-- Get the equave and the equave in cents
	local equave = input_mos.equave
	local equave_in_cents = rat.cents(equave)
	local equave_as_ratio = rat.as_ratio(equave)
	
	-- Get the step counts and number of periods
	local nL = input_mos.nL			-- Number of large steps per equave
	local ns = input_mos.ns			-- Number of small steps per equave
	local n = utils._gcd(nL, ns)	-- Number of periods
	local x = round(nL / n)			-- Number of large steps per period
	local y = round(ns / n)			-- Number of small steps per period
	
	-- Construct the sentence
	sentence = " is "
	
	-- Is the scale nonoctave?
	if equave_in_cents == 1200 then
		sentence = sentence .. string.format("an [[octave-equivalent]] [[moment of symmetry]] scale")
	else
		sentence = sentence .. string.format("a [[non-octave]], [[%s]]-equivalent [[moment_of_symmetry]] scale", equave_as_ratio)
	end
	
	-- What are the step counts? Should the word "step" contain an s?
	local s_nl = ""
	local s_ns = ""
	if nL ~= 1 then
		s_nl = "s"
	end
	if ns ~= 1 then
		s_ns = "s"
	end
	sentence = sentence .. string.format(" containing %d large step%s and %d small step%s", nL, s_nl, ns, s_ns)
	
	-- How does it repeat?
	local s_x = ""
	local s_y = ""
	local repetition = ""
	if x ~= 1 then
		s_x = "s"
	end
	if y ~= 1 then
		s_y = "s"
	end
	if n == 2 then
		repetition = "twice"
	elseif n > 2 then
		repetition = string.format("%d times", n)
	end
	if n == 1 and equave_in_cents == 1200 then
		sentence = sentence .. string.format(" and repeating every [[octave]].")
	elseif n == 1 and equave_in_cents ~= 1200 then
		sentence = sentence .. string.format(" and repeating every interval of %s (%.3f¢).", equave_as_ratio, equave_in_cents)
	elseif n ~= 1 and equave_in_cents == 1200 then
		sentence = sentence .. string.format(", with a [[period]] of %d large step%s and %d small step%s", x, s_x, y, s_y)
		sentence = sentence .. string.format(" that repeats every %.3f¢, or %s every [[octave]].", equave_in_cents / n, repetition)
	elseif n ~= 1 and equave_in_cents ~= 1200 then
		sentence = sentence .. string.format(", with a [[period]] of %d large step%s and %d small step%s", x, s_x, y, s_y)
		sentence = sentence .. string.format(" that repeats every %.3f¢, or %s every interval of %s (%.3f¢).", equave_in_cents / n, repetition, equave_as_ratio, equave_in_cents)
	end
	
	return sentence
end

-- Helper function
-- Calculates the generator ranges of the mos
function p.mos_intro_generator_ranges(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	-- Get the step counts and number of periods
	local nL = input_mos.nL			-- Number of large steps per equave
	local ns = input_mos.ns			-- Number of small steps per equave
	local n = utils._gcd(nL, ns)		-- Number of periods
	
	-- Get the equave as a ratio and in cents
	local equave = input_mos.equave
	local equave_in_cents = rat.cents(equave)
	
	-- Get the eds (ets) corresponding to the collapsed and equalized mosses
	local collapsed_et = et.new(nL, input_mos.equave)
	local equalized_et = et.new(nL + ns, input_mos.equave)
	
	-- Get the sizes of the bright generator for the collapsed and equalized et in steps
	-- These are used to calculate cent values for the generators
	-- The values for the dark generator are the period complements
	local generator = mos.bright_gen(input_mos)
	local gen_collapsed_in_steps = generator["L"]
	local gen_equalized_in_steps = generator["L"] + generator["s"]
	local bright_gen_max = et.cents(collapsed_et, gen_collapsed_in_steps)
	local bright_gen_min = et.cents(equalized_et, gen_equalized_in_steps)
	
	local dark_gen_min = equave_in_cents / n - bright_gen_max
	local dark_gen_max = equave_in_cents / n - bright_gen_min
	
	local sentence = string.format("[[Generating intervals|generator]] that produce this scale range from %.3f¢ to %.3f¢, or from %.3f¢ to %.3f¢.", bright_gen_min, bright_gen_max, dark_gen_min, dark_gen_max)
	
	return sentence
end

-- Function that creates a mos intro, given a mos and any other names
-- Intro (or lead section) consists of multiple sentences, and each is called individually
function p.mos_intro(input_mos, other_names)
	local input_mos = input_mos or mos.new(5, 2)
	local other_names = other_names or "name1; name2; name3"
	
	-- Get tamnams names
	local tamnams_name = mos.tamnams_name[scale_sig] or ""
	
	-- Construct the sentence
	local scalesig_and_names = p.mos_intro_scalesig_and_tamnams_names(input_mos, tamnams_name, other_names)
	local steps_and_periods = p.mos_intro_steps_and_periods(input_mos)
	local gens = p.mos_intro_generator_ranges(input_mos)
	
	return string.format("%s %s %s", scalesig_and_names, steps_and_periods, gens)
end

-- Function for use with a template
function p.mos_intro_frame(frame)
	-- Get and parse the the mos's scale signature, in the form xL ys or xL ys <p/q>
	local input_mos = mos.parse(frame.args['Scale Signature']) or mos.new(5, 2, 2)
	local other_names = frame.args['Other Names'] or ""
	
	return p.mos_intro(input_mos, other_names)
end

return p