Module:MOS intro: Difference between revisions
Non-octave equaves now displays as a JI ratio and cent value |
ArrowHead294 (talk | contribs) mNo edit summary |
||
| (75 intermediate revisions by 4 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
-- | local et = require("Module:ET") | ||
function p. | local mos = require("Module:MOS") | ||
local | local rat = require("Module:Rational") | ||
local tamnams = require("Module:TAMNAMS") | |||
local tip = require("Module:Template input parse") | |||
local utils = require("Module:Utils") | |||
local yesno = require("Module:Yesno") | |||
-- 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 | |||
-- 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 | 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 | |||
local | -- 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 the | -- Get all the mos's names, starting with tamnams names if applicable | ||
local | 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 | 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 | ||
local | end | ||
-- Helper function | |||
-- Determines what mos the given mos descends from | |||
-- as well as what step ratio that produces this scale | |||
function p.find_mos_ancestor(input_mos) | |||
local input_mos = input_mos or mos.new(7, 7) | |||
local z = input_mos.nL | |||
local | local w = input_mos.ns | ||
local generations = 0 | |||
-- | -- For an ancestral mos zU wv and descendant xL ys, how many steps of size | ||
local | -- 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 } | |||
-- | while (z ~= w) and (z + w > 10) do | ||
local m1 = math.max(z, w) | |||
local m2 = math.min(z, w) | |||
-- 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 | end | ||
-- | return mos.new(z, w, input_mos.equave), lg_chunk, sm_chunk, generations | ||
-- | end | ||
if | |||
-- Helper function | |||
-- What mos does the input mos descend from? | |||
function p.mos_descends_from(input_mos) | |||
local input_mos = input_mos or mos.new(7, 7) | |||
local ancestor_mos, lg_chunk, sm_chunk, generations = p.find_mos_ancestor(input_mos) | |||
--[[ | |||
-- Calculate the range of step ratios the ancestor should have | |||
-- 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 | ||
first_ancestor_step_ratio = string.format("%d:%d", num2, den2) | |||
second_ancestor_step_ratio = string.format("%d:%d", num1, den1) | |||
end | end | ||
if | |||
-- Step ratio range as text | |||
local step_ratio_range = string.format("%s to %s", first_ancestor_step_ratio, second_ancestor_step_ratio) | |||
-- Step ratio range as a named range | |||
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 | ||
]]-- | |||
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 | else | ||
descendant_text = string.format("%s is related to [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos)) | |||
end | end | ||
-- Add | --if named_range == "" then | ||
if | -- descendant_text = descendant_text .. string.format(", produced by such scales with a [[step ratio]] within the range of %s.", step_ratio_range) | ||
intro = intro .. "[[octave]]" | -- descendant_text = descendant_text .. "." | ||
--else | |||
-- descendant_text = descendant_text .. string.format(", produced by such scales with a [[step ratio]] within the %s range (%s).", named_range, step_ratio_range) | |||
-- descendant_text = descendant_text .. "." | |||
--end | |||
descendant_text = descendant_text .. string.format(", expanding it by %d tones.", input_mos.nL + input_mos.ns - ancestor_mos.nL - ancestor_mos.ns) | |||
return descendant_text | |||
end | |||
-- Main function (updated) | |||
function p._mos_intro(input_mos, other_names) | |||
local input_mos = input_mos or mos.new(5, 5, 3) | |||
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 | |||
-- 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 | |||
if rat.eq(input_mos.equave, rat.new(2)) then | |||
intro = intro .. " is a 2/1-equivalent ([[octave equivalence|octave-equivalent]]) [[moment of symmetry]] scale" | |||
elseif rat.eq(input_mos.equave, rat.new(3)) then | |||
intro = intro .. " is a 3/1-equivalent ([[tritave]]-equivalent) [[moment of symmetry]] scale" | |||
elseif rat.eq(input_mos.equave, rat.new(3,2)) then | |||
intro = intro .. " is a 3/2-equivalent (fifth-equivalent) [[moment of symmetry]] scale" | |||
else | else | ||
intro = intro .. " | intro = intro .. string.format(" is a %s-equivalent ([[nonoctave|non-octave]]) [[moment of symmetry]] scale", equave_as_ratio) | ||
end | end | ||
-- Add | -- 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 | if n == 1 then | ||
intro = intro .. ". " | 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)) | ||
else | else | ||
intro = intro .. ", or every " .. | 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 | end | ||
-- Add the generator | -- TODO: add descendant info | ||
local | if equave_in_cents == 1200 and nL + ns > 10 and nL ~= ns then | ||
local | intro = intro .. " " .. p.mos_descends_from(input_mos) | ||
intro = intro .. " | end | ||
intro = intro .. " | |||
-- 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]]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) | |||
-- Rothenberg propriety (rothenprop) info | |||
if ns == 1 then | |||
intro = intro .. " Scales of this form are always [[proper]] because there is only one small step." | |||
elseif ns / n == 1 then | |||
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." | |||
end | |||
return intro | return intro | ||
| Line 112: | Line 296: | ||
-- Function for use with a template | -- Function for use with a template | ||
function p. | 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[ | local input_mos = mos.parse(frame.args["Scale Signature"]) or mos.new(5, 2, 2) | ||
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 | return frame:preprocess(result) | ||
end | end | ||
return p | return p | ||