Module:MOS intro: Difference between revisions

Ganaram inukshuk (talk | contribs)
m Fixing up rounding
ArrowHead294 (talk | contribs)
mNo edit summary
 
(53 intermediate revisions by 3 users not shown)
Line 1: Line 1:
local mos = require('Module:MOS')
local rat = require('Module:Rational')
local utils = require('Module:Utils')
local et = require('Module:ET')
local p = {}
local p = {}


-- Helper function that parses entries from a semicolon-delimited string and returns them in an array
local et = require("Module:ET")
function p.parse_entries(unparsed)
local mos = require("Module:MOS")
local parsed = {}
local rat = require("Module:Rational")
for entry in string.gmatch(unparsed, '([^;]+)') do
local tamnams = require("Module:TAMNAMS")
table.insert(parsed, entry) -- Add to array
local tip = require("Module:Template input parse")
end
local utils = require("Module:Utils")
return parsed
local yesno = require("Module:Yesno")
end
 
-- TODO:
-- - Possible cleanup/rewording
-- - Possible official deprecation of other names; focus should be on what a mos
--  is, not what it's called.


-- Helper function
-- Helper function
Line 45: Line 45:
-- 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_scalesig_and_tamnams_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 63: Line 60:
if tamnams_names_list ~= "" and other_names_list ~= "" then
if tamnams_names_list ~= "" and other_names_list ~= "" then
-- There are both tamnams names and alternate names
-- There are both tamnams names and alternate names
sentence = sentence .. string.format(", named %s (also known as %s),", tamnams_names_list, other_names_list)
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
elseif tamnams_names_list ~= "" and other_names_list == "" then
-- There are only tamnams names
-- There are only tamnams names
sentence = sentence .. string.format(", named %s,", tamnams_names_list)
sentence = sentence .. string.format(", named %s in [[TAMNAMS]],", tamnams_names_list)
elseif tamnams_names_list == "" and other_names_list ~= "" then
elseif tamnams_names_list == "" and other_names_list ~= "" then
-- There are no tamnams names but there are alternate names
-- There are no tamnams names but there are alternate names
sentence = sentence .. string.format(", also known as %s,", other_names_list)
sentence = sentence .. string.format(", also called %s,", other_names_list)
end
end
Line 76: Line 73:


-- Helper function
-- Helper function
-- Introduces the mos by its step counts and number of periods
-- Determines what mos the given mos descends from
-- This is meant to immediately follow the sentence provided by:
-- as well as what step ratio that produces this scale
-- - mos_intro_tamnams_names()
function p.find_mos_ancestor(input_mos)
function p.mos_intro_steps_and_periods(input_mos)
local input_mos = input_mos or mos.new(7, 7)
local input_mos = input_mos or mos.new(3, 6, 3)
local z = input_mos.nL
local w = input_mos.ns
local generations = 0
-- For an ancestral mos zU wv and descendant xL ys, how many steps of size
-- L and s can fit inside U and v? (basically the chunking operation)
local lg_chunk = { nL = 1, ns = 0 }
local sm_chunk = { nL = 0, ns = 1 }
-- Get the equave and the equave in cents
while (z ~= w) and (z + w > 10) do
local equave = input_mos.equave
local m1 = math.max(z, w)
local equave_in_cents = rat.cents(equave)
local m2 = math.min(z, w)
local equave_as_ratio = rat.as_ratio(equave)
-- For use with updating ancestor mos chunks
local z_prev = z
-- Count how many generations
generations = generations + 1
-- Update step ratios
z = m2
w = m1 - m2
-- Update large chunk
local prev_lg_chunk = { nL = lg_chunk.nL, ns = lg_chunk.ns }
lg_chunk.nL = lg_chunk.nL + sm_chunk.nL
lg_chunk.ns = lg_chunk.ns + sm_chunk.ns
-- Update small chunk
if z ~= z_prev then
sm_chunk = prev_lg_chunk
end
end
-- Get the step counts and number of periods
return mos.new(z, w, input_mos.equave), lg_chunk, sm_chunk, generations
local nL = input_mos.nL -- Number of large steps per equave
end
local ns = input_mos.ns -- Number of small steps per equave
 
local n = utils._gcd(nL, ns) -- Number of periods
-- Helper function
local x = round(nL / n) -- Number of large steps per period
-- What mos does the input mos descend from?
local y = round(ns / n) -- Number of small steps per period
function p.mos_descends_from(input_mos)
local input_mos = input_mos or mos.new(7, 7)
-- Construct the sentence
local ancestor_mos, lg_chunk, sm_chunk, generations = p.find_mos_ancestor(input_mos)
sentence = " is "
-- Is the scale nonoctave?
--[[
if equave_in_cents == 1200 then
-- Calculate the range of step ratios the ancestor should have
sentence = sentence .. string.format("an [[octave equivalence|octave-equivalent]] [[moment of symmetry]] scale")
-- Sort ratios by hardness
local num1 = lg_chunk.nL + lg_chunk.ns
local den1 = sm_chunk.nL + sm_chunk.ns
local num2 = lg_chunk.nL
local den2 = sm_chunk.nL
local first_ancestor_step_ratio = ""
local second_ancestor_step_ratio = ""
if num1/den1 < num2/den2 then
first_ancestor_step_ratio = string.format("%d:%d", num1, den1)
second_ancestor_step_ratio = string.format("%d:%d", num2, den2)
else
else
sentence = sentence .. string.format("a [[non-octave]] [[moment of symmetry]] scale", equave_as_ratio)
first_ancestor_step_ratio = string.format("%d:%d", num2, den2)
second_ancestor_step_ratio = string.format("%d:%d", num1, den1)
end
end
-- What are the step counts? Should the word "step" contain an s?
-- Step ratio range as text
local s_nl = ""
local step_ratio_range = string.format("%s to %s", first_ancestor_step_ratio, second_ancestor_step_ratio)
local s_ns = ""
if nL ~= 1 then
-- Step ratio range as a named range
s_nl = "s"
local named_range = ""
if step_ratio_range == "1:1 to 2:1" then
named_range = "soft-of-basic"
elseif step_ratio_range == "2:1 to 1:0" then
named_range = "hard-of-basic"
elseif step_ratio_range == "1:1 to 3:2" then
named_range = "soft"
elseif step_ratio_range == "3:2 to 2:1" then
named_range = "hyposoft"
elseif step_ratio_range == "2:1 to 3:1" then
named_range = "hypohard"
elseif step_ratio_range == "3:1 to 1:0" then
named_range = "hard"
elseif step_ratio_range == "1:1 to 4:3" then
named_range = "ultrasoft"
elseif step_ratio_range == "4:3 to 3:2" then
named_range = "parasoft"
elseif step_ratio_range == "3:2 to 5:3" then
named_range = "quasisoft"
elseif step_ratio_range == "5:3 to 2:1" then
named_range = "minisoft"
elseif step_ratio_range == "2:1 to 5:2" then
named_range = "minihard"
elseif step_ratio_range == "5:2 to 3:1" then
named_range = "quasihard"
elseif step_ratio_range == "3:1 to 4:1" then
named_range = "parahard"
elseif step_ratio_range == "4:1 to 1:0" then
named_range = "ultrahard"
end
end
if ns ~= 1 then
]]--
s_ns = "s"
local descendant_text = ""
if generations == 1 then
descendant_text = string.format("%s is a child scale of [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos))
elseif generations == 2 then
descendant_text = string.format("%s is a grandchild scale of [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos))
elseif generations == 3 then
descendant_text = string.format("%s is a great-grandchild scale of [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos))
else
descendant_text = string.format("%s is related to [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos))
end
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
--if named_range == "" then
local round = 3
-- descendant_text = descendant_text .. string.format(", produced by such scales with a [[step ratio]] within the range of %s.", step_ratio_range)
local equave_rounded = utils._round_dec(equave_in_cents, round)
-- descendant_text = descendant_text .. "."
local period_rounded = utils._round_dec(equave_in_cents / n, round)
--else
-- Should step be singular or plural?
-- descendant_text = descendant_text .. string.format(", produced by such scales with a [[step ratio]] within the %s range (%s).", named_range, step_ratio_range)
local s_x = ""
-- descendant_text = descendant_text .. "."
local s_y = ""
--end
local repetition = ""
if x ~= 1 then
descendant_text = descendant_text .. string.format(", expanding it by %d tones.", input_mos.nL + input_mos.ns - ancestor_mos.nL - ancestor_mos.ns)
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]] (%f¢).", 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 %f¢, 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 %f¢, or %s every interval of %s (%f¢).", period_rounded, repetition, equave_as_ratio, equave_rounded)
end
return sentence
return descendant_text
end
end


-- Helper function
-- Main function (updated)
-- Calculates the brightest mode of the true mos
function p._mos_intro(input_mos, other_names)
function p.mos_intro_step_pattern(input_mos)
local input_mos = input_mos or mos.new(5, 5, 3)
local input_mos = input_mos or mos.new(5, 2)
local other_names = ""
-- Scale sig
local scale_sig = mos.as_string(input_mos)
-- Tamnams names, if any
local tamnams_name = tamnams.lookup_name(input_mos) or ""
-- Parsed names
local tamnams_pasred = tip.parse_entries(tamnams_name)
local other_parsed = tip.parse_entries(other_names)
-- Step counts for large steps, small steps, and number of periods
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)
local period_in_cents = equave_in_cents / n
-- How many decimal places to round to?
local round = 1
local brightest_mode = mos.brightest_mode(input_mos)
-- 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)
local sentence = string.format("Scales of the true MOS form take on a [[step pattern]] of '''%s''' or some [[rotation]] thereof.", brightest_mode)
-- Add equave equivalence
 
if rat.eq(input_mos.equave, rat.new(2)) then
return sentence
intro = intro .. " is a 2/1-equivalent ([[octave equivalence|octave-equivalent]]) [[moment of symmetry]] scale"
end
elseif rat.eq(input_mos.equave, rat.new(3)) then
 
intro = intro .. " is a 3/1-equivalent ([[tritave]]-equivalent) [[moment of symmetry]] scale"
-- Helper function
elseif rat.eq(input_mos.equave, rat.new(3,2)) then
-- Calculates the generator ranges of the mos
intro = intro .. " is a 3/2-equivalent (fifth-equivalent) [[moment of symmetry]] scale"
function p.mos_intro_generator_ranges(input_mos)
else
local input_mos = input_mos or mos.new(5, 2)
intro = intro .. string.format(" is a %s-equivalent ([[nonoctave|non-octave]]) [[moment of symmetry]] scale", equave_as_ratio)
end
-- 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"))
-- Get the step counts and number of periods
-- Add repetition
local nL = input_mos.nL -- Number of large steps per equave
if n == 1 then
local ns = input_mos.ns -- Number of small steps per equave
intro = intro .. ", repeating every " .. (equave_in_cents == 1200 and "[[octave]]." or string.format(" interval of [[%s]] (%.1f{{cent}}).", equave_as_ratio, equave_in_cents, round))
local n = utils._gcd(nL, ns) -- Number of periods
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 %.1f{{cent}}", period_in_cents)
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(" interval of [[%s]] (%.1f{{cent}}).", equave_as_ratio, equave_in_cents, round))
end
-- Get the equave as a ratio and in cents
-- TODO: add descendant info
local equave = input_mos.equave
if equave_in_cents == 1200 and nL + ns > 10 and nL ~= ns then
local equave_in_cents = rat.cents(equave)
intro = intro .. " " .. p.mos_descends_from(input_mos)
end
-- Add generator ranges
-- Get the eds (ets) corresponding to the collapsed and equalized mosses
-- Get the eds (ets) corresponding to the collapsed and equalized mosses
local collapsed_et = et.new(nL, input_mos.equave)
local collapsed_et = et.new(nL, input_mos.equave)
Line 191: Line 278:
local dark_gen_max = equave_in_cents / n - bright_gen_min
local dark_gen_max = equave_in_cents / n - bright_gen_min
-- Round values
local bright_gen_min_r = tostring(utils._round_dec(bright_gen_min, round))
local round = 3
local bright_gen_max_r = tostring(utils._round_dec(bright_gen_max, round))
local bright_gen_min_r = utils._round_dec(bright_gen_min, round)
local dark_gen_min_r = tostring(utils._round_dec(dark_gen_min, round))
local bright_gen_max_r = utils._round_dec(bright_gen_max, round)
local dark_gen_max_r = tostring(utils._round_dec(dark_gen_max, round))
local dark_gen_min_r = utils._round_dec(dark_gen_min, round)
local dark_gen_max_r = utils._round_dec(dark_gen_max, round)
local sentence = string.format("[[generator|Generating intervals]] that produce this scale range from %to %, or from %to %.", bright_gen_min_r, bright_gen_max_r, dark_gen_min_r, dark_gen_max_r)
intro = intro .. string.format(" [[Generator]]s that produce this scale range from %s{{cent}} to %s{{cent}}, or from %s{{cent}} to %s{{cent}}.", bright_gen_min_r, bright_gen_max_r, dark_gen_min_r, dark_gen_max_r)
return sentence
-- Rothenberg propriety (rothenprop) info
end
if ns == 1 then
 
intro = intro .. " Scales of this form are always [[proper]] because there is only one small step."
-- Function that creates a mos intro, given a mos and any other names
elseif ns / n == 1 then
-- Intro (or lead section) consists of multiple sentences, and each is called individually
intro = intro .. " Scales of the true MOS form, where every period is the same, are [[proper]] because there is only one small step per period."
function p.mos_intro(input_mos, other_names)
end
local input_mos = input_mos or mos.new(5, 2)
local other_names = other_names or "name1; name2; name3"
-- Get the scale sig
return intro
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_scalesig_and_tamnams_names(input_mos, 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
end


Line 231: Line 298:
function p.mos_intro_frame(frame)
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>
-- 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 input_mos = mos.parse(frame.args["Scale Signature"]) or mos.new(5, 2, 2)
local other_names = frame.args['Other Names'] or ""
local other_names = frame.args["Other Names"] or ""
local depparams = (other_names ~= "" and " [[Category:Pages with deprecated parameters]]" or "")
    local result = p._mos_intro(input_mos, other_names) .. depparams
    local debugg = yesno(frame.args["debug"])
   
    -- Debugger option
    if debugg == true then
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
end
return p.mos_intro(input_mos, other_names)
return frame:preprocess(result)
end
end


return p
return p