Module:MOS intro: Difference between revisions
Jump to navigation
Jump to search
Fixed logic for step ratio range names |
chromatic/enharmonic -> (great)(grand)child/descendant, per discussions with fredg999 regarding use of the term "chromatic" |
||
| Line 167: | Line 167: | ||
local descendant_text = "" | local descendant_text = "" | ||
if generations == 1 then | if generations == 1 then | ||
descendant_text = string.format("%s is a | 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 | elseif generations == 2 then | ||
descendant_text = string.format("%s is | 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 a descendant scale of [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos)) | descendant_text = string.format("%s is a descendant scale of [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos)) | ||
Revision as of 06:30, 4 February 2024
- 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 | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||||||||||||||||||||||||||||||||
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
-- 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
-- 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 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 }
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
return mos.new(z, w, input_mos.equave), lg_chunk, sm_chunk, generations
end
-- 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(10, 5)
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
first_ancestor_step_ratio = string.format("%d:%d", num2, den2)
second_ancestor_step_ratio = string.format("%d:%d", num1, den1)
end
-- 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
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 a descendant scale of [[%s]]", mos.as_string(input_mos), mos.as_string(ancestor_mos))
end
if named_range == "" then
descendant_text = descendant_text .. string.format(", produced by such scales with a [[step ratio]] within the range of %s.", step_ratio_range)
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)
end
--return string.format("Lg chunk: %d Ls, %d s's; sm chunk: %d L's, %d s's", lg_chunk.nL, lg_chunk.ns, sm_chunk.nL, sm_chunk.ns)
return descendant_text
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
if equave_in_cents == 1200 and nL ~= 1 and nL + ns > 10 then
intro = intro .. " " .. p.mos_descends_from(input_mos)
end
-- 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