「モジュール:TAMNAMS」の版間の差分
編集の要約なし |
編集の要約なし |
||
| (2人の利用者による、間の10版が非表示) | |||
| 1行目: | 1行目: | ||
local p = {} | local p = {} | ||
local mos = require("Module:MOS") | |||
local rat = require("Module:Rational") | |||
-- TODO | |||
--Function to parse a UDP and (possibly) scale degrees. | |||
--Separate interval/degree lookup into separate functions for for abbrevs and non-abbrev formats. | |||
--Added arbitrary hardness lookup for a single ratio (e.g., passing in 13:8 would return "quasisoft". | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--------------------------- | --------------------------- NAME LOOKUP TABLES --------------------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Lookup table for tamnams step ratios | -- Lookup table for tamnams step ratios | ||
p. | p.step_ratios = { | ||
['1:1'] = '均一', | ['1:1'] = '均一', | ||
['4:3'] = '緩慢', | ['4:3'] = '緩慢', | ||
| 21行目: | 24行目: | ||
['3:1'] = '硬質', | ['3:1'] = '硬質', | ||
['4:1'] = '硬骨', | ['4:1'] = '硬骨', | ||
['1:0'] = ' | ['1:0'] = '縮退' | ||
} | } | ||
-- And step ratio ranges | -- And step ratio ranges | ||
p. | p.step_ratio_ranges = { | ||
[ | ["1:1 to 2:1"] = "soft-of-basic", | ||
[ | ["1:1 to 3:2"] = "soft", | ||
[ | ["1:1 to 4:3"] = "ultrasoft", | ||
[ | ["4:3 to 3:2"] = "parasoft", | ||
[ | ["3:2 to 2:1"] = "hyposoft", | ||
[ | ["3:2 to 5:3"] = "quasisoft", | ||
[ | ["5:3 to 2:1"] = "minisoft", | ||
[ | ["2:1 to 5:2"] = "minihard", | ||
[ | ["5:2 to 3:1"] = "quasihard", | ||
[ | ["2:1 to 3:1"] = "hypohard", | ||
[ | ["3:1 to 4:1"] = "parahard", | ||
[ | ["4:1 to 1:0"] = "ultrahard", | ||
["3:1 to 1:0"] = "hard", | |||
["2:1 to 1:0"] = "hard-of-basic" | |||
} | } | ||
-- Lookup table for tamnams extended step ratios | -- Lookup table for tamnams extended step ratios | ||
p. | p.step_ratios_ext = { | ||
[ | ["1:1"] = "equalized", | ||
[ | ["6:5"] = "semiequalized", | ||
[ | ["4:3"] = "supersoft", | ||
[ | ["3:2"] = "soft", | ||
[ | ["5:3"] = "semisoft", | ||
[ | ["2:1"] = "basic", | ||
[ | ["5:2"] = "semihard", | ||
[ | ["3:1"] = "hard", | ||
[ | ["4:1"] = "superhard", | ||
[ | ["6:1"] = "extrahard", | ||
[ | ["10:1"] = "semicollapsed", | ||
[ | ["1:0"] = "collapsed" | ||
} | } | ||
-- And extended step ratio ranges | -- And extended step ratio ranges | ||
p. | p.step_ratio_ranges_ext = { | ||
[ | ["1:1 to 2:1"] = "soft-of-basic", | ||
[ | ["1:1 to 3:2"] = "soft", | ||
[ | ["1:1 to 6:5"] = "pseudoequalized", | ||
[ | ["6:5 to 4:3"] = "ultrasoft", | ||
[ | ["4:3 to 3:2"] = "parasoft", | ||
[ | ["3:2 to 2:1"] = "hyposoft", | ||
[ | ["3:2 to 5:3"] = "quasisoft", | ||
[ | ["5:3 to 2:1"] = "minisoft", | ||
[ | ["2:1 to 5:2"] = "minihard", | ||
[ | ["5:2 to 3:1"] = "quasihard", | ||
[ | ["2:1 to 3:1"] = "hypohard", | ||
[ | ["3:1 to 4:1"] = "parahard", | ||
[ | ["4:1 to 6:1"] = "hyperhard", | ||
[ | ["6:1 to 10:1"] = "clustered", | ||
[ | ["4:1 to 10:1"] = "ultrahard", | ||
[ | ["10:1 to 1:0"] = "pseudocollapsed", | ||
["3:1 to 1:0"] = "hard", | |||
["2:1 to 1:0"] = "hard-of-basic" | |||
} | } | ||
-- Lookup table for tamnams names within the range of 6-10 steps | -- Lookup table for tamnams names within the range of 6-10 steps | ||
p. | p.mos_names = { | ||
[ | ["1L 1s"] = "Monowood", | ||
[ | ["2L 2s"] = "Biwood", | ||
[ | ["1L 5s"] = "Antimachinoid", | ||
[ | ["2L 4s"] = "Malic", | ||
[ | ["3L 3s"] = "Triwood", | ||
[ | ["4L 2s"] = "Citric", | ||
[ | ["5L 1s"] = "Machinoid", | ||
[ | ["1L 6s"] = "Onyx", | ||
[ | ["2L 5s"] = "Antidiatonic", | ||
[ | ["3L 4s"] = "Mosh", | ||
[ | ["4L 3s"] = "Smitonic", | ||
[ | ["5L 2s"] = "Diatonic", | ||
[ | ["6L 1s"] = "Archaeotonic", | ||
[ | ["1L 7s"] = "Antipine", | ||
[ | ["2L 6s"] = "Subaric", | ||
[ | ["3L 5s"] = "Checkertonic", | ||
[ | ["4L 4s"] = "Tetrawood", | ||
[ | ["5L 3s"] = "Oneirotonic", | ||
[ | ["6L 2s"] = "Ekic", | ||
[ | ["7L 1s"] = "Pine", | ||
[ | ["1L 8s"] = "Antisubneutralic", | ||
[ | ["2L 7s"] = "Balzano", | ||
[ | ["3L 6s"] = "Tcherepnin", | ||
[ | ["4L 5s"] = "Gramitonic", | ||
[ | ["5L 4s"] = "Semiquartal", | ||
[ | ["6L 3s"] = "Hyrulic", | ||
[ | ["7L 2s"] = "Armotonic", | ||
[ | ["8L 1s"] = "Subneutralic", | ||
[ | ["1L 9s"] = "Antisinatonic", | ||
[ | ["2L 8s"] = "Jaric", | ||
[ | ["3L 7s"] = "Sephiroid", | ||
[ | ["4L 6s"] = "Lime", | ||
[ | ["5L 5s"] = "Pentawood", | ||
[ | ["6L 4s"] = "Lemon", | ||
[ | ["7L 3s"] = "Dicoid", | ||
[ | ["8L 2s"] = "Taric", | ||
[ | ["9L 1s"] = "Sinatonic" | ||
} | } | ||
-- And prefixes | -- And prefixes | ||
p. | p.mos_prefixes = { | ||
[ | ["1L 1s"] = "monwd", | ||
[ | ["2L 2s"] = "biwd", | ||
[ | ["2L 3s"] = "pent", | ||
[ | ["1L 5s"] = "amech", | ||
[ | ["2L 4s"] = "mal", | ||
[ | ["3L 3s"] = "triwd", | ||
[ | ["4L 2s"] = "citro", | ||
[ | ["5L 1s"] = "mech", | ||
[ | ["1L 6s"] = "on", | ||
[ | ["2L 5s"] = "pel", | ||
[ | ["3L 4s"] = "mosh", | ||
[ | ["4L 3s"] = "smi", | ||
[ | ["5L 2s"] = "dia", | ||
[ | ["6L 1s"] = "arch", | ||
[ | ["1L 7s"] = "apine", | ||
[ | ["2L 6s"] = "subar", | ||
[ | ["3L 5s"] = "check", | ||
[ | ["4L 4s"] = "tetrawd", | ||
[ | ["5L 3s"] = "oneiro", | ||
[ | ["6L 2s"] = "ek", | ||
[ | ["7L 1s"] = "pine", | ||
[ | ["1L 8s"] = "ablu", | ||
[ | ["2L 7s"] = "bal", | ||
[ | ["3L 6s"] = "cher", | ||
[ | ["4L 5s"] = "gram", | ||
[ | ["5L 4s"] = "cthon", | ||
[ | ["6L 3s"] = "hyru", | ||
[ | ["7L 2s"] = "arm", | ||
[ | ["8L 1s"] = "blu", | ||
[ | ["1L 9s"] = "asina", | ||
[ | ["2L 8s"] = "jara", | ||
[ | ["3L 7s"] = "seph", | ||
[ | ["4L 6s"] = "lime", | ||
[ | ["5L 5s"] = "pentawd", | ||
[ | ["6L 4s"] = "lem", | ||
[ | ["7L 3s"] = "dico", | ||
[ | ["8L 2s"] = "tara", | ||
["9L 1s"] = "sina" | |||
} | } | ||
-- And abbrevs | -- And abbrevs | ||
p. | p.mos_abbrevs = { | ||
[ | ["1L 1s"] = "w", | ||
[ | ["2L 2s"] = "bw", | ||
[ | ["1L 5s"] = "amk", | ||
[ | ["2L 4s"] = "mal", | ||
[ | ["3L 3s"] = "tw", | ||
[ | ["4L 2s"] = "cit", | ||
[ | ["5L 1s"] = "mk", | ||
[ | ["1L 6s"] = "on", | ||
[ | ["2L 5s"] = "pel", | ||
[ | ["3L 4s"] = "mosh", | ||
[ | ["4L 3s"] = "smi", | ||
[ | ["5L 2s"] = "dia", | ||
[ | ["6L 1s"] = "arc", | ||
[ | ["1L 7s"] = "ap", | ||
[ | ["2L 6s"] = "sb", | ||
[ | ["3L 5s"] = "chk", | ||
[ | ["4L 4s"] = "ttw", | ||
[ | ["5L 3s"] = "onei", | ||
[ | ["6L 2s"] = "ek", | ||
[ | ["7L 1s"] = "p", | ||
[ | ["1L 8s"] = "ablu", | ||
[ | ["2L 7s"] = "bz", | ||
[ | ["3L 6s"] = "ch", | ||
[ | ["4L 5s"] = "gm", | ||
[ | ["5L 4s"] = "ct", | ||
[ | ["6L 3s"] = "hy", | ||
[ | ["7L 2s"] = "arm", | ||
[ | ["8L 1s"] = "blu", | ||
[ | ["1L 9s"] = "asi", | ||
[ | ["2L 8s"] = "ja", | ||
[ | ["3L 7s"] = "sp", | ||
[ | ["4L 6s"] = "lm", | ||
[ | ["5L 5s"] = "pw", | ||
[ | ["6L 4s"] = "le", | ||
[ | ["7L 3s"] = "di", | ||
[ | ["8L 2s"] = "ta", | ||
[ | ["9L 1s"] = "si" | ||
} | |||
-- TAMNAMS equave-agnostic names | |||
p.equave_agnostic_names = { | |||
["1L 1s"] = "trivial", | |||
["1L 2s"] = "antrial", | |||
["2L 1s"] = "trial", | |||
["1L 3s"] = "antetric", | |||
["3L 1s"] = "tetric", | |||
["1L 4s"] = "pedal", | |||
["2L 3s"] = "pentic", | |||
["3L 2s"] = "anpentic", | |||
["4L 1s"] = "manual" | |||
} | |||
-- And prefixes | |||
p.equave_agnostic_prefixes = { | |||
["1L 1s"] = "trv", | |||
["1L 2s"] = "at", | |||
["2L 1s"] = "t", | |||
["1L 3s"] = "att", | |||
["3L 1s"] = "tt", | |||
["1L 4s"] = "pd", | |||
["2L 3s"] = "pt", | |||
["3L 2s"] = "apt", | |||
["4L 1s"] = "mnu" | |||
} | |||
-- And abbrevs | |||
p.equave_agnostic_abbrevs = { | |||
["1L 1s"] = "trv", | |||
["1L 2s"] = "at", | |||
["2L 1s"] = "t", | |||
["1L 3s"] = "att", | |||
["3L 1s"] = "tt", | |||
["1L 4s"] = "ped", | |||
["2L 3s"] = "pt", | |||
["3L 2s"] = "apt", | |||
["4L 1s"] = "mnu" | |||
} | } | ||
| 210行目: | 257行目: | ||
if type(step_ratio) == "string" then | if type(step_ratio) == "string" then | ||
return step_ratio | return step_ratio | ||
elseif (type(step_ratio) == "table" and type(step_ratio[1]) == | elseif (type(step_ratio) == "table" and type(step_ratio[1]) == "number" and type(step_ratio[2]) == "number") then | ||
return rat.new(step_ratio[1], step_ratio[2]) | return rat.new(step_ratio[1], step_ratio[2]) | ||
else | else | ||
| 219行目: | 266行目: | ||
-- Mosses for name lookup are entered either as a scalesig or as a mos as | -- Mosses for name lookup are entered either as a scalesig or as a mos as | ||
-- defined in the mos module. If of the latter, it's converted into a textual | -- defined in the mos module. If of the latter, it's converted into a textual | ||
-- scalesig. | -- scalesig. Scalesigs should have a normal space, not a nonbreaking space, as | ||
-- the lookup tables use a normal space. | |||
function p.preprocess_scalesig(input_mos) | function p.preprocess_scalesig(input_mos) | ||
if type(input_mos) == "string" then | if type(input_mos) == "string" then | ||
return | local parsed_mos = mos.parse(input_mos) | ||
return mos.as_string(parsed_mos, false) | |||
elseif type(input_mos) == "table" then | elseif type(input_mos) == "table" then | ||
return mos.as_string(input_mos) | return mos.as_string(input_mos, false) | ||
else | else | ||
return nil | return nil | ||
| 231行目: | 280行目: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
----------------------------- LOOKUP FUNCTIONS | ------------------------ TEMPLATE HELPER FUNCTIONS ----------------------------- | ||
-------------------------------------------------------------------------------- | |||
-- Verifier function that checks for a prefix already provided by TAMNAMS. | |||
-- If there is one provided, use that. If there is none, use the one passed in. | |||
-- If no prefix is provided, defualt to "mos". If "NONE" is entered, return an | |||
-- empty string. | |||
function p.verify_prefix(input_mos, mos_prefix) | |||
local mos_prefix = p.lookup_prefix(input_mos) or mos_prefix or "mos" | |||
if mos_prefix == "NONE" or mos_prefix == "none" then | |||
mos_prefix = "" | |||
elseif mos_prefix == "" then | |||
mos_prefix = "mos" | |||
end | |||
return mos_prefix | |||
end | |||
-- Verifier function that checks for an abbrev already provided by TAMNAMS. | |||
-- If there is one provided, use that. If there is none, use the one passed in. | |||
-- If no abbrev is provided, defualt to "m". If "NONE" is entered, return an | |||
-- empty string. | |||
function p.verify_abbrev(input_mos, mos_abbrev) | |||
local mos_abbrev = p.lookup_abbrev(input_mos) or mos_abbrev or "m" | |||
if mos_abbrev == "NONE" or mos_abbrev == "none" then | |||
mos_abbrev = "" | |||
elseif mos_abbrev == "" then | |||
mos_abbrev = "m" | |||
end | |||
return mos_abbrev | |||
end | |||
-------------------------------------------------------------------------------- | |||
------------------------- NAME LOOKUP FUNCTIONS -------------------------------- | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
| 238行目: | 319行目: | ||
function p.lookup_name(input_mos) | function p.lookup_name(input_mos) | ||
local scalesig = p.preprocess_scalesig(input_mos) | local scalesig = p.preprocess_scalesig(input_mos) | ||
return p. | return p.mos_names[scalesig] | ||
end | end | ||
| 245行目: | 326行目: | ||
function p.lookup_prefix(input_mos) | function p.lookup_prefix(input_mos) | ||
local scalesig = p.preprocess_scalesig(input_mos) | local scalesig = p.preprocess_scalesig(input_mos) | ||
return p. | return p.mos_prefixes[scalesig] | ||
end | end | ||
| 252行目: | 333行目: | ||
function p.lookup_abbrev(input_mos) | function p.lookup_abbrev(input_mos) | ||
local scalesig = p.preprocess_scalesig(input_mos) | local scalesig = p.preprocess_scalesig(input_mos) | ||
return p. | return p.mos_abbrevs[scalesig] | ||
end | end | ||
| 263行目: | 344行目: | ||
-- Produce the key needed to lookup the step ratio name | -- Produce the key needed to lookup the step ratio name | ||
-- use_extended is used to toggle between central range and extended range | -- use_extended is used to toggle between central range and extended range | ||
local key = rat.as_ratio(step_ratio, | local key = rat.as_ratio(step_ratio, ":") | ||
local named_ratio = use_extended and p. | local named_ratio = use_extended and p.step_ratios_ext[key] or p.step_ratios[key] | ||
return named_ratio | return named_ratio | ||
end | end | ||
| 282行目: | 363行目: | ||
local float_2 = rat.as_float(step_ratio_2) | local float_2 = rat.as_float(step_ratio_2) | ||
if (float_1 > float_2) then | if (float_1 > float_2) then | ||
key = string.format( | key = string.format("%s to %s", rat.as_ratio(step_ratio_2, ":"), rat.as_ratio(step_ratio_1, ":")) | ||
else | else | ||
key = string.format( | key = string.format("%s to %s", rat.as_ratio(step_ratio_1, ":"), rat.as_ratio(step_ratio_2, ":")) | ||
end | end | ||
-- use_extended is used to toggle between central range and extended range | -- use_extended is used to toggle between central range and extended range | ||
local named_ratio_range = use_extended and p. | local named_ratio_range = use_extended and p.step_ratio_ranges_ext[key] or p.step_ratio_ranges[key] | ||
return named_ratio_range | |||
end | |||
-------------------------------------------------------------------------------- | |||
------------------------- NAME FINDER FUNCTIONS -------------------------------- | |||
-------------------------------------------------------------------------------- | |||
-- Helper function | |||
-- "Rounds" step ratios up to the nearest named ratio | |||
function p.step_ratio_ceil(step_ratio) | |||
local hardness = step_ratio[1] / step_ratio[2] | |||
return | local rounded_step_ratio = nil | ||
if hardness > 1/1 and hardness <= 4/3 then | |||
rounded_step_ratio = {4,3} | |||
elseif hardness > 4/3 and hardness <= 3/2 then | |||
rounded_step_ratio = {3,2} | |||
elseif hardness > 3/2 and hardness <= 5/3 then | |||
rounded_step_ratio = {5,3} | |||
elseif hardness > 5/3 and hardness <= 2/1 then | |||
rounded_step_ratio = {2,1} | |||
elseif hardness > 2/1 and hardness <= 5/2 then | |||
rounded_step_ratio = {5,2} | |||
elseif hardness > 5/2 and hardness <= 3/1 then | |||
rounded_step_ratio = {3,1} | |||
elseif hardness > 3/1 and hardness <= 4/1 then | |||
rounded_step_ratio = {4,1} | |||
elseif hardness > 4/1 and hardness <= 1/0 then | |||
rounded_step_ratio = {1,0} | |||
end | |||
return rounded_step_ratio | |||
end | |||
-- Helper function | |||
-- "Rounds" step ratios down to the nearest named ratio | |||
function p.step_ratio_floor(step_ratio) | |||
local hardness = step_ratio[1] / step_ratio[2] | |||
local rounded_step_ratio = nil | |||
if hardness >= 1/1 and hardness < 4/3 then | |||
rounded_step_ratio = {1,1} | |||
elseif hardness >= 4/3 and hardness < 3/2 then | |||
rounded_step_ratio = {4,3} | |||
elseif hardness >= 3/2 and hardness < 5/3 then | |||
rounded_step_ratio = {3,2} | |||
elseif hardness >= 5/3 and hardness < 2/1 then | |||
rounded_step_ratio = {5,3} | |||
elseif hardness >= 2/1 and hardness < 5/2 then | |||
rounded_step_ratio = {2,1} | |||
elseif hardness >= 5/2 and hardness < 3/1 then | |||
rounded_step_ratio = {5,2} | |||
elseif hardness >= 3/1 and hardness < 4/1 then | |||
rounded_step_ratio = {3,1} | |||
elseif hardness >= 4/1 and hardness < 1/0 then | |||
rounded_step_ratio = {4,1} | |||
end | |||
return rounded_step_ratio | |||
end | |||
-- Function for finding the smallest step ratio range that encompasses the two | |||
-- ratios passed in. | |||
function p.find_step_ratio_range_for_ratio_pair(step_ratio_1, step_ratio_2, use_extended) | |||
local use_extended = use_extended == true -- Does nothing for now | |||
-- Swap ratios so they're in the right order | |||
local hardness_1 = step_ratio_1[1] / step_ratio_1[2] | |||
local hardness_2 = step_ratio_2[1] / step_ratio_2[2] | |||
local lower_ratio = nil | |||
local upper_ratio = nil | |||
local named_ratio_range = "" | |||
if hardness_1 <= hardness_2 then | |||
lower_ratio = p.step_ratio_floor(step_ratio_1) | |||
upper_ratio = p.step_ratio_ceil (step_ratio_2) | |||
else | |||
lower_ratio = p.step_ratio_floor(step_ratio_2) | |||
upper_ratio = p.step_ratio_ceil (step_ratio_1) | |||
end | |||
-- If one ratio corresponds to the endpoint of a named hardness range | |||
-- but the other ratio exceeds that of a smaller range, default to the | |||
-- largest range that would accommodate it. | |||
-- 2:1 to (L:s > 3:1) = hard-of-basic | |||
-- 4:1 and up = ultrahard | |||
-- (L:s < 3:2) to 2:1 = soft-of-basic | |||
-- 4:3 and lower = ultrasoft | |||
if (lower_ratio[1]/lower_ratio[2] == 2/1 and upper_ratio[1]/upper_ratio[2] > 3/1) or lower_ratio[1]/lower_ratio[2] == 4/1 then | |||
upper_ratio = {1,0} | |||
elseif (upper_ratio[1]/upper_ratio[2] == 2/1 and lower_ratio[1]/lower_ratio[2] < 3/2) or upper_ratio[1]/upper_ratio[2] == 4/3 then | |||
lower_ratio = {1,1} | |||
end | |||
local named_ratio_range = p.lookup_step_ratio_range(lower_ratio, upper_ratio, use_extended) | |||
return named_ratio_range | |||
end | end | ||
| 312行目: | 485行目: | ||
end | end | ||
return | return mos.new(z, w, input_mos.equave) | ||
end | end | ||
| 432行目: | 605行目: | ||
if abbrev_format == "abbrev" or abbrev_format == "ABBREV" then | if abbrev_format == "abbrev" or abbrev_format == "ABBREV" then | ||
return string.format("%s%d% | return string.format("%s%d%sd", quality, step_count, mos_prefix) | ||
elseif abbrev_format == "shortened" or abbrev_format == "SHORTENED" then | elseif abbrev_format == "shortened" or abbrev_format == "SHORTENED" then | ||
return string.format("%s %d-%sd.", quality, step_count, mos_prefix) | return string.format("%s %d-%sd.", quality, step_count, mos_prefix) | ||
| 627行目: | 800行目: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
----------------------------- | -------------------- MODE NOTATION/COMPARISON FUNCTIONS ------------------------ | ||
-------------------------- BASED ON UDP NOTATION ------------------------------- | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
function p. | -- Given the number of gens up and down per period and a period count (u, d, and | ||
local input_mos = mos. | -- p respectively), construct the UDP as a string, as up|dp(p) or u|d, for | ||
local | -- multi-period and single-period respectively. | ||
local | -- If only u is known, then d = p - u - 1. | ||
-- If only d is known, then u = p - d - 1. (Basically, u + d + 1 = steps/p.) | |||
-- A table of alterations can also be passed in, assuming they are already | |||
-- written out (EG, d3md or, if notation is established, b4). | |||
function p.udp_as_string(gens_up_per_period, gens_down_per_period, periods, alterations) | |||
local periods = periods or 1 | |||
local alterations_as_string = "" | |||
if alterations ~= nil then | |||
for i = 1, #alterations do | |||
alterations_as_string = alterations_as_string .. " " .. alterations[i] | |||
end | |||
end | |||
return (periods == 1 | |||
and string.format("%s{{pipe}}%s", gens_up_per_period, gens_down_per_period) | |||
or string.format("%s{{pipe}}%s(%s)", gens_up_per_period * periods, gens_down_per_period * periods, periods)) | |||
.. alterations_as_string | |||
end | |||
-- Given an input mos, list the udps for each of its modes, listed in order of | |||
-- decreasing brightness. | |||
function p.mos_mode_udps(input_mos) | |||
local steps_per_period = mos.period_step_count(input_mos) | |||
local period_count = mos.period_count(input_mos) | |||
local udps = {} | |||
for i = 1, steps_per_period do | |||
local gens_up = steps_per_period - i | |||
local gens_down = steps_per_period - gens_up - 1 | |||
local udp = p.udp_as_string(gens_up, gens_down, period_count) | |||
table.insert(udps, udp) | |||
end | |||
return udps | |||
end | |||
-- Given an input mos, list the cpos for each of its modes. The circular | |||
-- permutation orderings are listed starting from the brightest mode. | |||
-- Example with 5L 2s modes | |||
-- MODE NAME | UDP | CPO | |||
-- ------------+-----+----- | |||
-- Lydian | 6|0 | 1 | |||
-- Ionian | 5|1 | 5 | |||
-- Mixolydian | 4|2 | 2 | |||
-- Dorian | 3|3 | 6 | |||
-- Aeolian | 2|4 | 3 | |||
-- Phrygian | 1|5 | 7 | |||
-- Lociran | 0|6 | 4 | |||
function p.mos_mode_cpos(input_mos) | |||
local steps_per_period = mos.period_step_count(input_mos) | |||
local period_count = mos.period_count(input_mos) | |||
local steps_per_bright_gen = mos.bright_gen_step_count(input_mos) | |||
local cpos = {} | |||
for i = 1, steps_per_period do | |||
local cpo = ((i - 1) * steps_per_bright_gen) % steps_per_period + 1 | |||
table.insert(cpos, cpo) | |||
end | |||
return cpos | |||
end | |||
-- Given a string that represents a mode, return the udps for each of its | |||
-- rotations. If the mode is a modmos, the closest mode and its alterations | |||
-- are returned as a string. | |||
-- NOTES: | |||
-- - A period of repetition will always have as many modes as there are steps | |||
-- in that period. If it were any less, then the true period of repetition | |||
-- is a substring of that. | |||
function p.mode_rotation_udps(input_mode, input_mos, mos_abbrev, use_brightest_mode_search) | |||
local use_brightest_mode_search = use_brightest_mode_search == nil or use_brightest_mode_search | |||
local modes = mos.mode_rotations(input_mode) | |||
local udps = {} | |||
for i = 1, #modes do | |||
table.insert(udps, p.mode_udp(modes[i], input_mos, mos_abbrev, use_brightest_mode_search)) | |||
end | |||
return udps | |||
end | |||
-- Given a string that represents a mode, return its udp. | |||
-- Helper function for mode_rotation_udps(). | |||
-- If a mode is for a modmos, it will return the closest brightest mode followed | |||
-- by its altered scale degrees. Alterations require a mos abbrev, which is | |||
-- automatically looked up, defaulting to "m" if no abbrev can be found. | |||
-- NOTES: | |||
-- - This is inefficient when used on true-mos modes since it's a brute-force | |||
-- search for what mode it's closest to. It is, however, effective on | |||
-- modmosses, which is the primary use for this function. | |||
function p.mode_udp(input_mode, input_mos, mos_abbrev, use_brightest_mode_search) | |||
local use_brightest_mode_search = use_brightest_mode_search == nil or use_brightest_mode_search | |||
local mos_abbrev = mos_abbrev or p.lookup_abbrev(input_mos) or "m" | |||
local true_modes = mos.modes_to_step_matrices(input_mos) | |||
local input_mode_as_step_matrix = mos.mode_to_step_matrix(input_mode) | |||
-- For each mode, count the number of differences between each true mode | |||
-- and the entered mode and keep track of which mode has the fewest diffs. | |||
-- That mode is considered the closest mode, whose UDP will be used for the | |||
-- mode name, followed by which scale degrees are changed. | |||
-- If the number of diffs is ever zero, then the entered mode was a true-mos | |||
-- mode and has zero alterations. | |||
local lowest_differences = #input_mode_as_step_matrix | |||
local bright_gens_down_per_period = 0 | |||
local differences = {} | |||
for i = 1, #true_modes do | |||
local current_true_mode = true_modes[i] | |||
local current_differences = p.differences_between_modes(current_true_mode, input_mode_as_step_matrix) | |||
-- It's possible for more than one mode to be closest. The tiebreaker is | |||
-- whichever mode is brightest (or darkest, which can be toggled). | |||
if use_brightest_mode_search then | |||
if #current_differences < lowest_differences then | |||
-- Brightest-closest match | |||
bright_gens_down_per_period = i - 1 | |||
lowest_differences = #current_differences | |||
differences = current_differences | |||
end | |||
else | |||
if #current_differences <= lowest_differences then | |||
-- Darkest-closest match | |||
bright_gens_down_per_period = i - 1 | |||
lowest_differences = #current_differences | |||
differences = current_differences | |||
end | |||
end | |||
end | |||
-- Parse the differences as scale degrees. | |||
-- The differences between the true mode and the input mode are denoted as | |||
-- interval vectors. These should be parsed into a list of altered scale | |||
-- degrees before being passed into udp_as_string. Coding it this way allows | |||
-- for the possibility of adding custom mos notation (EG, diamond-mos). | |||
local alterations = {} | |||
for i = 1, #differences do | |||
table.insert(alterations, p.degree_quality(differences[i], input_mos, "ABBREV", mos_abbrev)) | |||
end | |||
-- Produce the UDP (as text) for the mode, formatted as up|dp(p) for multi- | |||
-- period mosses, or u|d for single-period mosses. | |||
local period_count = mos.period_count(input_mos) | |||
local bright_gens_up_per_period = mos.period_step_count(input_mos) - 1 - bright_gens_down_per_period | |||
udp = p.udp_as_string(bright_gens_up_per_period, bright_gens_down_per_period, period_count, alterations) | |||
return udp | |||
end | |||
-- Helper function for mode_udp, but can be used standalone. | |||
-- Given two modes as step matrices, produce a list of differences between the | |||
-- base mode and the altered mode. Diffs are listed as an array of mosstep | |||
-- vectors (a table containing the number of L's and s's for that interval). | |||
function p.differences_between_modes(base_step_matrix, altered_step_matrix) | |||
local differences = {} | |||
for i = 1, #altered_step_matrix do | |||
local altered_interval = altered_step_matrix[i] | |||
-- If i is greater than the number of intervals in base_step_matrix, | |||
-- then the interval being accessed is an extra-equave interval. Instead | |||
-- of accessing the ith interval (which would lead to a nil error), | |||
-- access the corresponding equave-reduced interval, then raise it by | |||
-- the necessary number of equaves. | |||
local base_interval = {} | |||
if i > #base_step_matrix then | |||
local current_mossteps = i - 1 | |||
local equave_step_count = #base_step_matrix - 1 | |||
local equave_interval = base_step_matrix[#base_step_matrix] | |||
local equave_reduced_interval = base_step_matrix[current_mossteps % equave_step_count + 1] | |||
local equave_count = math.floor(current_mossteps / equave_step_count) | |||
local equaves = mos.interval_mul(equave_interval, equave_count) | |||
base_interval = mos.interval_add(equave_reduced_interval, equaves) | |||
else | |||
base_interval = base_step_matrix[i] | |||
end | |||
if not mos.interval_eq(base_interval, altered_interval) then | |||
table.insert(differences, altered_interval) | |||
end | |||
end | |||
return differences | |||
end | |||
-- 日本語対応用 | |||
function p.interval_quality_japanese(interval, input_mos) | |||
-- Get the step count of the interval. The sum of L's and s's will always | |||
-- determine what k-mosstep the interval is. | |||
local step_count = mos.interval_step_count(interval) | |||
-- Decode the quality | |||
local quality = p.decode_quality_japanese(interval, input_mos, abbrev_format) | |||
return string.format("%s%d度", quality, step_count) | |||
end | |||
function p.degree_quality_japanese(interval, input_mos) | |||
-- Get the step count of the interval. The sum of L's and s's will always | |||
-- determine what k-mosstep the interval is. | |||
local step_count = mos.interval_step_count(interval) | |||
-- Decode the quality | |||
local quality = p.decode_quality_japanese(interval, input_mos) | |||
return string.format("%s%d度", quality, step_count) | |||
end | |||
local | function p.decode_quality_japanese(interval, input_mos) | ||
-- Normalize the interval so negative values aren't being used. | |||
local interval = mos.normalize_interval(interval) | |||
-- Get the step count of the interval. The sum of L's and s's will always | |||
-- | -- determine what k-mosstep the interval is. | ||
local step_count = mos.interval_step_count(interval) | |||
-- Determine what "special" type the interval is so that the designations | |||
-- of augmented/perfect/diminished (APd) apply, skipping major/minor (Mm). | |||
-- If it's the period or equave, then it's a multiple of the period. | |||
-- If it's any one of the gens, then reducing it should produce that gen. | |||
local is_period = step_count % mos.period_step_count(input_mos) == 0 | |||
local is_bright_gen = step_count % mos.period_step_count(input_mos) == mos.bright_gen_step_count(input_mos) | |||
local is_dark_gen = step_count % mos.period_step_count(input_mos) == mos.dark_gen_step_count(input_mos) | |||
-- Special case: APd does not apply to a root mos's (nL ns) generators; | |||
-- instead, it's Mm. | |||
local is_root_mos = input_mos.nL == input_mos.ns | |||
-- Is perfectable? This is for intervals for which maj/min does not apply. | |||
local is_perfectable = is_period or (is_bright_gen and not is_root_mos) or (is_dark_gen and not is_root_mos) | |||
-- Get chroma count and adjust as needed | |||
local chroma_count = 0 | |||
if is_period then | |||
-- Chroma count 0 is the perfect size. This interval does not appear | |||
-- as any other size across all mos modes. | |||
chroma_count = mos.interval_chroma_count(interval, input_mos) | |||
elseif is_bright_gen and not is_root_mos then | |||
-- Chroma count 0 is the large size, and -1 the small size; these | |||
-- are perfect and diminished respectively. | |||
chroma_count = mos.interval_chroma_count(interval, input_mos) | |||
elseif is_dark_gen and not is_root_mos then | |||
-- Chroma count 0 is the large size, and -1 the small size; these | |||
-- are augmented and perfect respectively. Since the perfect size | |||
-- corresponds to a chroma count of -1, pass in -1 as the 3rd arg. | |||
chroma_count = mos.interval_chroma_count(interval, input_mos, -1) | |||
else | |||
-- Chroma count 0 is the large size, and -1 the small size; these are | |||
-- major and minor respectively. | |||
chroma_count = mos.interval_chroma_count(interval, input_mos) | |||
end | |||
-- Get absolute value of chroma count | |||
local chroma_abs = math.abs(chroma_count) | |||
local quality = "" | |||
if is_perfectable then | |||
-- Get the quality for perfectable intervals | |||
if chroma_count < 0 then | |||
quality = "減" | |||
elseif chroma_count > 0 then | |||
quality = "増" | |||
else | |||
quality = "完全" | |||
end | |||
else | |||
-- Get the quality for nonperfectable intervals | |||
-- Is the interval major? If not, decrement chroma_abs by 1 | |||
local is_positive = chroma_count >= 0 | |||
chroma_abs = is_positive and chroma_abs or chroma_abs - 1 | |||
if chroma_abs > 0 and is_positive then | |||
quality = "増" | |||
elseif chroma_abs > 0 and not is_positive then | |||
quality = "減" | |||
else | |||
quality = is_positive and "長" or "短" | |||
end | |||
if chroma_abs > 1 then | |||
quality = string.format("%d× %s", chroma_abs, quality) | |||
end | end | ||
end | end | ||
return | |||
return quality | |||
end | |||
-------------------------------------------------------------------------------- | |||
----------------------------- TESTER FUNCTION ---------------------------------- | |||
-------------------------------------------------------------------------------- | |||
function p.tester() | |||
local input_mos = mos.new(5, 2) | |||
return p.preprocess_scalesig(input_mos) | |||
end | end | ||
return p | return p | ||