Module:MOS intro: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Inthar (talk | contribs)
No edit summary
Ganaram inukshuk (talk | contribs)
Changed to revamped function (now includes rothenberg propriety), while maintaining the same wording as before
Line 3: Line 3:
local utils = require('Module:Utils')
local utils = require('Module:Utils')
local et = require('Module:ET')
local et = require('Module:ET')
local tip = require('Module:Template input parse')
local p = {}
local p = {}


Line 45: Line 46:
-- Introduces the mos by its scale sig and names
-- Introduces the mos by its scale sig and names
-- Names must be entered as an array
-- Names must be entered as an array
function p.mos_intro_names(input_mos, tamnams_name, other_names)
function p.mos_intro_names(scale_sig, tamnams_name, other_names)
local input_mos = input_mos or mos.new(5, 2)
local scale_sig = scale_sig or "5L 2s"
local tamnams_name = tamnams_name or { "diatonic" }
local tamnams_name = tamnams_name or { "diatonic" }
local other_names = other_names or { "other-name" }
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
-- Get all the mos's names, starting with tamnams names if applicable
Line 220: Line 218:
-- Construct the sentence
-- Construct the sentence
local scalesig_and_names = p.mos_intro_names(input_mos, tamnams_parsed, other_parsed)
local scalesig_and_names = p.mos_intro_names(scale_sig, tamnams_parsed, other_parsed)
local steps_and_periods = p.mos_intro_steps_and_periods(input_mos)
local steps_and_periods = p.mos_intro_steps_and_periods(input_mos)
local step_pattern = p.mos_intro_step_pattern(input_mos)
local step_pattern = p.mos_intro_step_pattern(input_mos)
Line 226: Line 224:
return string.format("%s %s %s %s", scalesig_and_names, steps_and_periods, step_pattern, gens)
return string.format("%s %s %s %s", scalesig_and_names, steps_and_periods, step_pattern, gens)
end
-- Main function (updated)
function p._mos_intro(input_mos, other_names)
local input_mos = input_mos or mos.new(10, 2)
local other_names = other_names or "hemifourths"
-- Scale sig
local scale_sig = mos.as_string(input_mos)
-- Tamnams names, if any
local tamnams_name = mos.tamnams_name[scale_sig] or ""
-- Parsed names
local tamnams_pasred = tip.parse_entries(tamnams_name)
local other_parsed = tip.parse_entries(other_names)
-- Step counts
local nL = input_mos.nL
local ns = input_mos.ns
local n = utils._gcd(nL, ns)
-- Equave as ratio and cents
local equave_as_ratio = rat.as_ratio(input_mos.equave)
local equave_in_cents = rat.cents(input_mos.equave)
-- How many decimal places to round to?
local round = 3
-- Build up intro text, starting with the scale sig and scale names
-- This is done through the aid of a helper function
local intro = p.mos_intro_names(scale_sig, tamnams_pasred, other_parsed)
-- Add equave equivalence
intro = intro .. (equave_in_cents == 1200 and " is an [[octave equivalence|octave-equivalent]] [[moment of symmetry]] scale" or "is a [[nonoctave|non-octave]] [[moment of symmetry scale]]")
-- Add step counts
intro = intro .. string.format(" containing %d large %s", nL, (nL == 1 and "step" or "steps"))
intro = intro .. string.format(" and %d small %s", ns, (ns == 1 and "step" or "steps"))
-- Add repetition
if n == 1 then
intro = intro .. ", repeating every " .. (equave_in_cents == 1200 and "[[octave]]." or string.format(" interval of [[%s]] (%.3f).", equave_as_ratio, utils._round_dec(equave_in_cents, round)))
else
intro = intro .. string.format(", with a [[period]] of %d large %s", nL/n, (nL/n == 1 and "step" or "steps"))
intro = intro .. string.format(" and %d small %s", ns/n, (ns/n == 1 and "step" or "steps"))
intro = intro .. string.format(" that repeats every %.3f¢", equave_in_cents / n)
intro = intro .. (n == 2 and " or twice every" or string.format(" or %d times every", n)) .. (equave_in_cents == 1200 and " octave." or string.format(" every interval of [[%s]] (%.d¢).", equave_as_ratio, utils._round(equave_in_cents, round)))
end
-- TODO: add descendant info
-- Add generator ranges
-- 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 bright_gen_min_r = tostring(utils._round_dec(bright_gen_min, round))
local bright_gen_max_r = tostring(utils._round_dec(bright_gen_max, round))
local dark_gen_min_r = tostring(utils._round_dec(dark_gen_min, round))
local dark_gen_max_r = tostring(utils._round_dec(dark_gen_max, round))
intro = intro .. string.format(" [[generator|Generators]] that produce this scale range from %s¢ to %s¢, or from %s¢ to %s¢.", bright_gen_min_r, bright_gen_max_r, dark_gen_min_r, dark_gen_max_r)
-- Rothenberg propriety (rothenprop) info
if ns == 1 then
intro = intro .. "Scales of this form always exhibit [[proper|Rothenberg propriety]] because there is only one small step."
elseif ns / n == 1 then
intro = intro .. "Scales in which every period is the same sequence of steps always exhibit [[proper|Rothenberg propriety]] because there is only one small step per period."
end
return intro
end
end


Line 234: Line 317:
local other_names = frame.args['Other Names'] or ""
local other_names = frame.args['Other Names'] or ""
return p.mos_intro(input_mos, other_names)
return p._mos_intro(input_mos, other_names)
end
end


return p
return p

Revision as of 07:41, 3 February 2024

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 (9)
Line Function Params
9 parse_entries (unparsed)
19 mos_intro_list_names (mos_names, conjunction)
48 mos_intro_names (scale_sig, tamnams_name, other_names)
79 mos_intro_steps_and_periods (input_mos)
151 mos_intro_step_pattern (input_mos)
163 mos_intro_generator_ranges (input_mos)
205 mos_intro (input_mos, other_names)
229 _mos_intro (main) (input_mos, other_names)
314 mos_intro_frame (invokable) (frame)
Lua modules required (5)
Variable Module Functions used
et Module:ET new
cents
mos Module:MOS new
brightest_mode
bright_gen
as_string
parse
rat Module:Rational cents
as_ratio
tip Module:Template input parse parse_entries
utils Module:Utils _gcd
_round_dec
_round

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 tip = require('Module:Template input parse')
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
-- Names must be entered as an array
function p.mos_intro_names(scale_sig, tamnams_name, other_names)
	local scale_sig = scale_sig or "5L 2s"
	local tamnams_name = tamnams_name or { "diatonic" }
	local other_names = other_names or { "other-name" }
	
	-- Get all the mos's names, starting with tamnams names if applicable
	local tamnams_names_list = p.mos_intro_list_names(tamnams_name, "and")
	local other_names_list = p.mos_intro_list_names(other_names, "or")
	
	-- Construct the sentence
	local sentence = string.format("'''%s'''", scale_sig)
	
	-- Add names
	if tamnams_names_list ~= "" and other_names_list ~= "" then
		-- There are both tamnams names and alternate names
		sentence = sentence .. string.format(", named %s in [[TAMNAMS]] (also known as %s),", tamnams_names_list, other_names_list)
	elseif tamnams_names_list ~= "" and other_names_list == "" then
		-- There are only tamnams names
		sentence = sentence .. string.format(", named %s in [[TAMNAMS]],", tamnams_names_list)
	elseif tamnams_names_list == "" and other_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 equivalence|octave-equivalent]] [[moment of symmetry]] scale")
	else
		sentence = sentence .. string.format("a [[non-octave]] [[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 large is the period in cents? Round to 3 decimal places
	local round = 3
	local equave_rounded = tostring(utils._round_dec(equave_in_cents, round))
	local period_rounded = tostring(utils._round_dec(equave_in_cents / n, round))
	-- Should step be singular or plural?
	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(", repeating every [[octave]].")
	elseif n == 1 and equave_in_cents ~= 1200 then
		sentence = sentence .. string.format(", repeating every interval of [[%s]] (%s¢).", equave_as_ratio, equave_rounded)
	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 %s¢, or %s every [[octave]].", period_rounded, 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 %s¢, or %s every interval of %s (%s¢).", period_rounded, repetition, equave_as_ratio, equave_rounded)
	end
	
	return sentence
end

-- Helper function
-- Calculates the brightest mode of the true mos
function p.mos_intro_step_pattern(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	local brightest_mode = mos.brightest_mode(input_mos)
	
	local sentence = string.format("[[mode|Modes]] of this scale are rotations of the step pattern '''%s'''.", brightest_mode)

	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
	
	-- Round values
	local round = 3
	local bright_gen_min_r = tostring(utils._round_dec(bright_gen_min, round))
	local bright_gen_max_r = tostring(utils._round_dec(bright_gen_max, round))
	local dark_gen_min_r = tostring(utils._round_dec(dark_gen_min, round))
	local dark_gen_max_r = tostring(utils._round_dec(dark_gen_max, round))
	
	local sentence = string.format("[[generator|Generators]] that produce this scale range from %s¢ to %s¢, or from %s¢ to %s¢.", bright_gen_min_r, bright_gen_max_r, dark_gen_min_r, dark_gen_max_r)
	
	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 the scale sig
	local scale_sig = mos.as_string(input_mos)
	
	-- Get tamnams names
	local tamnams_name = mos.tamnams_name[scale_sig] or ""
	
	-- Parse names
	local tamnams_parsed = p.parse_entries(tamnams_name)
	local other_parsed = p.parse_entries(other_names)
	
	-- Construct the sentence
	local scalesig_and_names = p.mos_intro_names(scale_sig, tamnams_parsed, other_parsed)
	local steps_and_periods = p.mos_intro_steps_and_periods(input_mos)
	local step_pattern = p.mos_intro_step_pattern(input_mos)
	local gens = p.mos_intro_generator_ranges(input_mos)
	
	return string.format("%s %s %s %s", scalesig_and_names, steps_and_periods, step_pattern, gens)
end

-- Main function (updated)
function p._mos_intro(input_mos, other_names)
	local input_mos = input_mos or mos.new(10, 2)
	local other_names = other_names or "hemifourths"
	
	-- Scale sig
	local scale_sig = mos.as_string(input_mos)
	
	-- Tamnams names, if any
	local tamnams_name = mos.tamnams_name[scale_sig] or ""
	
	-- Parsed names
	local tamnams_pasred = tip.parse_entries(tamnams_name)
	local other_parsed = tip.parse_entries(other_names)
	
	-- Step counts
	local nL = input_mos.nL
	local ns = input_mos.ns
	local n = utils._gcd(nL, ns)
	
	-- Equave as ratio and cents
	local equave_as_ratio = rat.as_ratio(input_mos.equave)
	local equave_in_cents = rat.cents(input_mos.equave)
	
	-- How many decimal places to round to?
	local round = 3
	
	-- Build up intro text, starting with the scale sig and scale names
	-- This is done through the aid of a helper function
	local intro = p.mos_intro_names(scale_sig, tamnams_pasred, other_parsed)
	
	-- Add equave equivalence
	intro = intro .. (equave_in_cents == 1200 and " is an [[octave equivalence|octave-equivalent]] [[moment of symmetry]] scale" or "is a [[nonoctave|non-octave]] [[moment of symmetry scale]]")
	
	-- Add step counts
	intro = intro .. string.format(" containing %d large %s", nL, (nL == 1 and "step" or "steps"))
	intro = intro .. string.format(" and %d small %s", ns, (ns == 1 and "step" or "steps"))
	
	-- Add repetition
	if n == 1 then
		intro = intro .. ", repeating every " .. (equave_in_cents == 1200 and "[[octave]]." or string.format(" interval of [[%s]] (%.3f).", equave_as_ratio, utils._round_dec(equave_in_cents, round)))
	else
		intro = intro .. string.format(", with a [[period]] of %d large %s", nL/n, (nL/n == 1 and "step" or "steps"))
		intro = intro .. string.format(" and %d small %s", ns/n, (ns/n == 1 and "step" or "steps"))
		intro = intro .. string.format(" that repeats every %.3f¢", equave_in_cents / n)
		intro = intro .. (n == 2 and " or twice every" or string.format(" or %d times every", n)) .. (equave_in_cents == 1200 and " octave." or string.format(" every interval of [[%s]] (%.d¢).", equave_as_ratio, utils._round(equave_in_cents, round)))
	end
	
	-- TODO: add descendant info
	
	-- Add generator ranges
	-- 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 bright_gen_min_r = tostring(utils._round_dec(bright_gen_min, round))
	local bright_gen_max_r = tostring(utils._round_dec(bright_gen_max, round))
	local dark_gen_min_r = tostring(utils._round_dec(dark_gen_min, round))
	local dark_gen_max_r = tostring(utils._round_dec(dark_gen_max, round))
	
	intro = intro .. string.format(" [[generator|Generators]] that produce this scale range from %s¢ to %s¢, or from %s¢ to %s¢.", bright_gen_min_r, bright_gen_max_r, dark_gen_min_r, dark_gen_max_r)
	
	-- Rothenberg propriety (rothenprop) info
	if ns == 1 then
		intro = intro .. "Scales of this form always exhibit [[proper|Rothenberg propriety]] because there is only one small step."
	elseif ns / n == 1 then
		intro = intro .. "Scales in which every period is the same sequence of steps always exhibit [[proper|Rothenberg propriety]] because there is only one small step per period."
	end
	
	return intro
	
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