モジュール:TAMNAMS
ナビゲーションに移動
検索に移動
このモジュールについての説明文ページを モジュール:TAMNAMS/doc に作成できます
-- Module for TAMNAMS-related things as it pertains to mosses
-- This module is meant to be used with other modules, not as part of a template
-- Work in progress
local mos = require('Module:MOS')
local rat = require('Module:Rational')
local utils = require('Module:Utils')
local p = {}
--------------------------------------------------------------------------------
------------------------------- LOOKUP TABLES ----------------------------------
--------------------------------------------------------------------------------
-- Lookup table for tamnams step ratios
p.tamnams_ratios = {
['1:1'] = '均一',
['4:3'] = '緩慢',
['3:2'] = '緩衝',
['5:3'] = '緩和',
['2:1'] = '標準',
['5:2'] = '硬派',
['3:1'] = '硬質',
['4:1'] = '硬骨',
['1:0'] = '縮退'
}
-- And step ratio ranges
p.tamnams_ranges = {
['1:1 to 2:1'] = 'soft-of-basic',
['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',
['2:1 to 1:0'] = 'hard-of-basic'
}
-- Lookup table for tamnams extended step ratios
p.tamnams_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
p.tamnams_ranges_ext = {
['1:1 to 2:1'] = 'soft-of-basic',
['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',
['2:1 to 1:0'] = 'hard-of-basic'
}
-- Lookup table for tamnams names within the range of 6-10 steps
p.tamnams_name = {
['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
p.tamnams_prefix = {
['1L 1s'] = 'monwd',
['2L 2s'] = 'biwd',
['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
p.tamnams_abbrev = {
['1L 1s'] = 'wood',
['2L 2s'] = 'bw',
['1L 5s'] = 'amech',
['2L 4s'] = 'mal',
['3L 3s'] = 'trw',
['4L 2s'] = 'cit',
['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'] = 'chk',
['4L 4s'] = 'ttw',
['5L 3s'] = 'onei',
['6L 2s'] = 'ek',
['7L 1s'] = 'pine',
['1L 8s'] = 'ablu',
['2L 7s'] = 'bal',
['3L 6s'] = 'ch',
['4L 5s'] = 'gram',
['5L 4s'] = 'cth',
['6L 3s'] = 'hyru',
['7L 2s'] = 'arm',
['8L 1s'] = 'blu',
['1L 9s'] = 'asi',
['2L 8s'] = 'jar',
['3L 7s'] = 'seph',
['4L 6s'] = 'lime',
['5L 5s'] = 'pw',
['6L 4s'] = 'lem',
['7L 3s'] = 'dico',
['8L 2s'] = 'tar',
['9L 1s'] = 'si'
}
--------------------------------------------------------------------------------
------------------------------ HELPER FUNCTIONS --------------------------------
--------------------------------------------------------------------------------
-- Step ratios are entered as an array of two numeric values, or alternatively,
-- as a ratio as defined by the rational module. If of the former, this helper
-- function converts it to the latter. This preprocess step is for simplifying
-- ratios.
function p.preprocess_step_ratio(step_ratio)
if type(step_ratio) == "string" then
return step_ratio
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])
else
return nil
end
end
-- 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
-- scalesig.
function p.preprocess_scalesig(input_mos)
if type(input_mos) == "string" then
return input_mos
elseif type(input_mos) == "table" then
return mos.as_string(input_mos)
else
return nil
end
end
--------------------------------------------------------------------------------
----------------------------- LOOKUP FUNCTIONS ---------------------------------
--------------------------------------------------------------------------------
-- Function for looking up a mos's name (octave-equivalent mosses only).
-- Can accept either a mos (defined by mos module) or its scalesig.
function p.lookup_name(input_mos)
local scalesig = p.preprocess_scalesig(input_mos)
return p.tamnams_name[scalesig]
end
-- Function for looking up a mos's prefix (octave-equivalent mosses only).
-- Can accept either a mos (defined by mos module) or its scalesig.
function p.lookup_prefix(input_mos)
local scalesig = p.preprocess_scalesig(input_mos)
return p.tamnams_prefix[scalesig]
end
-- Function for looking up a mos's abbrev (octave-equivalent mosses only).
-- Can accept either a mos (defined by mos module) or its scalesig.
function p.lookup_abbrev(input_mos)
local scalesig = p.preprocess_scalesig(input_mos)
return p.tamnams_abbrev[scalesig]
end
-- Function for looking up a step ratio range
-- Module:Rational is used to help simplify ratios
function p.lookup_step_ratio(step_ratio, use_extended)
local step_ratio = p.preprocess_step_ratio(step_ratio)
local use_extended = use_extended == true
-- Produce the key needed to lookup the step ratio name
-- use_extended is used to toggle between central range and extended range
local key = rat.as_ratio(step_ratio, ':')
local named_ratio = use_extended and p.tamnams_ratios_ext[key] or p.tamnams_ratios[key]
return named_ratio ~= nil and named_ratio or key
end
-- Function for looking up a step ratio range
-- Module:Rational is used to help simplify ratios
function p.lookup_step_ratio_range(step_ratio_1, step_ratio_2, use_extended)
local step_ratio_1 = p.preprocess_step_ratio(step_ratio_1)
local step_ratio_2 = p.preprocess_step_ratio(step_ratio_2)
local use_extended = use_extended == true
-- Produce the key needed for the lookup table as a/b to c/d
-- Swap ratios if ratio 1 has a higher hardness than ratio 2
local key = ""
local float_1 = rat.as_float(step_ratio_1)
local float_2 = rat.as_float(step_ratio_2)
if (float_1 > float_2) then
key = string.format('%s to %s', rat.as_ratio(step_ratio_2, ':'), rat.as_ratio(step_ratio_1, ':'))
else
key = string.format('%s to %s', rat.as_ratio(step_ratio_1, ':'), rat.as_ratio(step_ratio_2, ':'))
end
-- use_extended is used to toggle between central range and extended range
local named_ratio_range = use_extended and p.tamnams_ranges_ext[key] or p.tamnams_ranges[key]
return named_ratio_range ~= nil and named_ratio_range or key
end
-- Given a mos, find the ancestor mos within the target note count.
function p.find_ancestor(input_mos, target_note_count)
local target_note_count = target_note_count or 10
local z = input_mos.nL
local w = input_mos.ns
while (z ~= w) and (z + w > target_note_count) do
local m1 = math.max(z, w)
local m2 = math.min(z, w)
-- For use with updating ancestor mos chunks
local z_prev = z
-- Update step ratios
z = m2
w = m1 - m2
end
return p.new(z, w, mos.equave)
end
-- Given a mos, find the ancestor mos within the target note count, while also
-- returning the step ancestor's step ratio range (as two ratios) and the number
-- of generations between the two mosses. (A more in-depth version of the prev.)
function p.find_ancestor_info(input_mos, target_step_count)
local target_step_count = target_step_count or 10
-- For an ancestor 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 z = input_mos.nL
local w = input_mos.ns
local lg_chunk = { nL = 1, ns = 0 }
local sm_chunk = { nL = 0, ns = 1 }
local generations = 0
while (z ~= w) and (z + w > target_step_count) 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
-- Translate chunks into step ratios
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 ratio_1, ratio_2
if num1/den1 < num2/den2 then
ratio_1 = { num1, den1 }
ratio_2 = { num2, den2 }
else
ratio_2 = { num1, den1 }
ratio_1 = { num2, den2 }
end
return mos.new(z, w, input_mos.equave), ratio_1, ratio_2, generations
end
--------------------------------------------------------------------------------
--------------------- MOSSTEP/MOSDEGREE QUALITY FUNCTIONS ----------------------
--------------------------------------------------------------------------------
-- Given an interval vector for a mos, produce the name for that interval.
-- Prefix lookup is done automatically if no prefix is provided; defaults to
-- "mos" if no prefix is found. Prefixes are used for the full name for an
-- interval, whereas abbrevs are used for the abbreviated form. (This is
-- because some abbrevs are shorter than the corresponding prefix.)
-- Formats are as follows:
-- - NONE: full name, eg "perfect 4-diastep"
-- - SENTENCE-CASE: same as NONE, but with capitalized first letter
-- - SHORTENED: shortened form, eg "Perf. 4-diastep"
-- - ABBREV: abbreviated form, eg "P4dias"
function p.interval_quality(interval, input_mos, abbrev_format, mos_prefix)
local abbrev_format = abbrev_format or "none"
local mos_prefix = mos_prefix
or (abbrev_format == "abbrev" and p.lookup_abbrev(input_mos) or p.lookup_prefix(input_mos))
or (abbrev_format == "abbrev" and "m" or "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(interval, input_mos, abbrev_format)
if abbrev_format == "abbrev" or abbrev_format == "ABBREV" then
return string.format("%s%d%ss", quality, step_count, mos_prefix)
elseif abbrev_format == "shortened" or abbrev_format == "SHORTENED" then
return string.format("%s %d-%ss.", quality, step_count, mos_prefix)
elseif abbrev_format == "sentence-case" or abbrev_format == "SENTENCE-CASE" then
return string.format("%s %d-%sstep", quality, step_count, mos_prefix)
else
return string.format("%s %d-%sstep", quality, step_count, mos_prefix)
end
end
-- Given an interval vector for a mos, produce the name for the scale degree
-- reached by going up that interval, from the root. (This is identical to the
-- previous function, except it uses "degree" instead of "step".)
-- Prefix lookup is done automatically, as with interval_quality().
-- Formats are as follows:
-- - NONE: full name, eg "perfect 4-diadegree"
-- - SENTENCE-CASE: same as NONE, but with capitalized first letter
-- - SHORTENED: shortened form, eg "Perf. 4-diadegree"
-- - ABBREV: abbreviated form, eg "P4diad"
function p.degree_quality(interval, input_mos, abbrev_format, mos_prefix)
local abbrev_format = abbrev_format or "none"
local mos_prefix = mos_prefix
or (abbrev_format == "abbrev" and p.lookup_abbrev(input_mos) or p.lookup_prefix(input_mos))
or (abbrev_format == "abbrev" and "m" or "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(interval, input_mos, abbrev_format)
if abbrev_format == "abbrev" or abbrev_format == "ABBREV" then
return string.format("%s%d%ss", quality, step_count, mos_prefix)
elseif abbrev_format == "shortened" or abbrev_format == "SHORTENED" then
return string.format("%s %d-%sd.", quality, step_count, mos_prefix)
elseif abbrev_format == "sentence-case" or abbrev_format == "SENTENCE-CASE" then
return string.format("%s %d-%sdegree", quality, step_count, mos_prefix)
else
return string.format("%s %d-%sdegree", quality, step_count, mos_prefix)
end
end
-- Decodes the quality of a mosstep. Helper function to interval_quality() and
-- degree_quality(), but can be used standalone if only the keyword (maj, min,
-- aug, perf, dim) is needed. The chroma amounts are as follows:
-- AMT| PERFECTABLE | NONPERFECTABLE | DARK GEN ONLY
-- ---+-----------------+-------------------+------------------
-- ...| . . . | . . . | . . .
-- 4 | 4x augmented | 4x augmented | 5x augmented
-- 3 | 3x augmented | 3x augmented | 4x augmented
-- 2 | 2x augmented | 2x augmented | 3x augmented
-- 1 | augmented | augmented | 4x augmented
-- 0 | perfect | major | augmented
-- -1 | diminished | minor | perfect
-- -2 | 2x diminished | diminished | diminished
-- -3 | 3x diminished | 2x diminished | 2x diminished
-- -4 | 4x diminished | 3x diminished | 3x diminished
-- -5 | 5x diminished | 4x diminished | 4x diminished
-- ...| . . . | . . . | . . .
function p.decode_quality(interval, input_mos, abbrev_format)
local abbrev_format = abbrev_format or "none" -- Default is no abbreviation
-- 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 abbrev_format == "none" or abbrev_format == "NONE" then
if chroma_count < 0 then
quality = "diminished"
elseif chroma_count > 0 then
quality = "augmented"
else
quality = "perfect"
end
if chroma_abs > 1 then
quality = string.format("%d× %s", chroma_abs, quality)
end
elseif abbrev_format == "sentence-case" or abbrev_format == "SENTENCE-CASE" then
if chroma_count < 0 then
quality = "Diminished"
elseif chroma_count > 0 then
quality = "Augmented"
else
quality = "Perfect"
end
if chroma_abs > 1 then
quality = string.format("%d× %s", chroma_abs, quality)
end
elseif abbrev_format == "shortened" or abbrev_format == "SHORTENED" then
if chroma_count < 0 then
quality = "Dim."
elseif chroma_count > 0 then
quality = "Aug."
else
quality = "Perf."
end
if chroma_abs > 1 then
quality = string.format("%d× %s", chroma_abs, quality)
end
elseif abbrev_format == "abbrev" or abbrev_format == "ABBREV" then
if chroma_count < 0 then
quality = "d"
elseif chroma_count > 0 then
quality = "A"
else
quality = "P"
end
if chroma_abs > 3 then
quality = string.format("%s<sup>%d</sup>", quality, chroma_abs)
elseif chroma_abs > 1 and chroma_abs <= 3 then
quality = string.rep(quality, chroma_abs)
end
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 abbrev_format == "none" or abbrev_format == "NONE" then
if chroma_abs > 0 and is_positive then
quality = "augmented"
elseif chroma_abs > 0 and not is_positive then
quality = "diminished"
else
quality = is_positive and "major" or "minor"
end
if chroma_abs > 1 then
quality = string.format("%d× %s", chroma_abs, quality)
end
elseif abbrev_format == "sentence-case" or abbrev_format == "SENTENCE-CASE" then
if chroma_abs > 0 and is_positive then
quality = "Augmented"
elseif chroma_abs > 0 and not is_positive then
quality = "Diminished"
else
quality = is_positive and "Major" or "Minor"
end
if chroma_abs > 1 then
quality = string.format("%d× %s", chroma_abs, quality)
end
elseif abbrev_format == "shortened" or abbrev_format == "SHORTENED" then
if chroma_abs > 0 and is_positive then
quality = "Aug."
elseif chroma_abs > 0 and not is_positive then
quality = "Dim."
else
quality = is_positive and "Maj." or "Min."
end
if chroma_abs > 1 then
quality = string.format("%d× %s", chroma_abs, quality)
end
elseif abbrev_format == "abbrev" or abbrev_format == "ABBREV" then
if chroma_abs > 0 and is_positive then
quality = "A"
elseif chroma_abs > 0 and not is_positive then
quality = "d"
else
quality = is_positive and "M" or "m"
end
if chroma_abs > 3 then
quality = string.format("%s<sup>%d</sup>", quality, chroma_abs)
elseif chroma_abs > 1 and chroma_abs <= 3 then
quality = string.rep(quality, chroma_abs)
end
end
end
return quality
end
--------------------------------------------------------------------------------
----------------------------- TESTER FUNCTION ----------------------------------
--------------------------------------------------------------------------------
function p.tester()
local input_mos = mos.new(4,3)
local brightest_mode = mos.brightest_mode(input_mos)
local interval_qualities = {}
local step_matrices = mos.modes_to_step_matrices(input_mos)
for i = 1, mos.equave_step_count(input_mos) do
local qualities = ""
for j = 1, #step_matrices[i] do
qualities = qualities .. p.decode_quality(step_matrices[i][j], input_mos, "shortened") .. " "
--qualities = qualities .. (step_matrices[i][j] ~= nil and "Y" or "N") .. " "
end
table.insert(interval_qualities, qualities)
end
return interval_qualities
end
return p