Module:Infobox MOS: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
m Renamed functions in preparation for rewrite
Ganaram inukshuk (talk | contribs)
Infobox mos now uses multisection infobox; not all links have been restored, however
Line 1: Line 1:
local p = {}
local p = {}
local i = require('Module:Interval')
local utils = require('Module:Utils')
local u = require('Module:Utils')
local rat = require('Module:Rational')
local rat = require('Module:Rational')
local MOS = require('Module:MOS')
local mos = require('Module:MOS')
local ET = require('Module:ET')
local et = require('Module:ET')
local xp = require('Module:Xenpaper')
--local xp = require('Module:Xenpaper') -- No xenpaper links for now
local infobox = require('Module:Infobox')
local infobox = require('Module:Infobox')


Line 21: Line 20:
['t'] = 3,
['t'] = 3,
}
}
function gcd(x, y)
if x < y then
x, y = y, x
end
while y > 0 do
x, y = y, x % y
end
return x
end
function display_range (arg1, arg2, arg3, arg4) --If arg1 < arg2, then display "arg3 to arg4"; otherwise display "arg4 to arg3".
if arg1 < arg2 then
return arg3 .. ' to ' .. arg4
else
return arg4 .. ' to ' .. arg3
end
end
function round(num, numDecimalPlaces)
  local mult = 10^(numDecimalPlaces or 0)
  return math.floor(num * mult + 0.5) / mult
end


-- Helper function
-- Helper function
Line 49: Line 25:
function p.categorize(tuning)
function p.categorize(tuning)
local tuning = tuning or "5L 2s"
local tuning = tuning or "5L 2s"
local input_mos = MOS.parse(tuning)
local input_mos = mos.parse(tuning)
-- Add to category of abstact mosses
-- Add to category of abstact mosses
Line 75: Line 51:
-- - Monosmall descendants that descend from 1L 9s aren't categorized
-- - Monosmall descendants that descend from 1L 9s aren't categorized
--  for now.
--  for now.
local ancestor_mos = MOS.find_ancestor(input_mos)
local ancestor_mos = mos.find_ancestor(input_mos)
local tamnams_name = MOS.tamnams_name[MOS.as_string(ancestor_mos)]
local tamnams_name = mos.tamnams_name[mos.as_string(ancestor_mos)]
if tamnams_name == "arch(a)eotonic" then
if tamnams_name == "arch(a)eotonic" then
Line 92: Line 68:
end
end


-- Test function
-- Helper function
--[[
-- Creates adjacent links for mos, found by +/-1 large or +/- small steps
function p.tester(min_notecount, max_notecount)
function p.adjacent_links(input_mos)
local min_notecount = min_notecount or 6
local input_mos = input_mos or mos.new(5, 2)
local max_notecount = max_notecount or 10
local result = ""
local long_equave_as_text = ""
local equave_as_text = ""
if not rat.eq(input_mos.equave, 2) then
long_equave_as_text = string.format(" (%s-equivalent)", rat.as_ratio(input_mos.equave))
equave_as_text = string.format("⟨%s⟩)", rat.as_ratio(input_mos.equave))
end
for i = min_notecount, max_notecount do
local adjacent_links = {
result = result .. string.format("%i-tone scales:\n", i)
string.format("[[%dL %ds%s | ↖%dL %ds%s]]", input_mos.nL-1, input_mos.ns-1, long_equave_as_text, input_mos.nL-1, input_mos.ns-1, equave_as_text),
for j = 1, i-1 do
string.format("[[%dL %ds%s | ↑%dL %ds%s]]", input_mos.nL  , input_mos.ns-1, long_equave_as_text, input_mos.nL  , input_mos.ns-1, equave_as_text),
local scalesig = string.format("%iL %is", j, i-j)
string.format("[[%dL %ds%s | %dL %ds%s↗]]", input_mos.nL+1, input_mos.ns-1, long_equave_as_text, input_mos.nL+1, input_mos.ns-1, equave_as_text),
local current_mos = MOS.parse(scalesig)
string.format("[[%dL %ds%s | ←%dL %ds%s]]", input_mos.nL-1, input_mos.ns  , long_equave_as_text, input_mos.nL-1, input_mos.ns  , equave_as_text),
local ancestor_mos = MOS.find_ancestor(current_mos)
string.format("[[%dL %ds%s | %dL %ds%s→]]", input_mos.nL+1, input_mos.ns  , long_equave_as_text, input_mos.nL+1, input_mos.ns  , equave_as_text),
local tamnams_name = MOS.tamnams_name[MOS.as_string(ancestor_mos)]
string.format("[[%dL %ds%s | ↙%dL %ds%s]]", input_mos.nL-1, input_mos.ns+1, long_equave_as_text, input_mos.nL-1, input_mos.ns+1, equave_as_text),
string.format("[[%dL %ds%s | ↓%dL %ds%s]]", input_mos.nL  , input_mos.ns+1, long_equave_as_text, input_mos.nL  , input_mos.ns+1, equave_as_text),
if tamnams_name == "arch(a)eotonic" then
string.format("[[%dL %ds%s | %dL %ds%s↘]]", input_mos.nL+1, input_mos.ns+1, long_equave_as_text, input_mos.nL+1, input_mos.ns+1, equave_as_text),
tamnams_name = "archaeotonic"
}
end
for i = 1, #adjacent_links do
if tamnams_name ~= nil then
local is_null_large = string.find(adjacent_links[i], "0L")
result = result .. string.format("TAMNAMS-named ancestor of %s: %s\n", scalesig, tamnams_name)
local is_null_small = string.find(adjacent_links[i], "0s")
else
result = result .. string.format("TAMNAMS-named ancestor of %s: ---\n", scalesig)
if is_null_large or is_null_small then
end
adjacent_links[i] = ""
end
end
result = result .. "\n"
end
end
return result
return adjacent_links
end
end
]]--


-- Main function
-- Helper function
-- To be called by wrapper
-- Produces section entries for scale sturcture
function p._infobox_MOS_old(tuning, other_names)
function p.scale_structure(input_mos)
local tuning = tuning or "5L 2s"
local input_mos = input_mos or mos.new(5, 5, 3)
local other_names = other_names or nil
local mos = MOS.parse(tuning)
local equave_as_text = rat.as_ratio(input_mos.equave)
local equave_in_cents = rat.cents(input_mos.equave)
local equave = mos.equave
local number_of_periods = utils._gcd(input_mos.nL, input_mos.ns)
local equave_link = ' (' .. rat.as_ratio(equave) .. '-equivalent)'
local period_as_text = ""
local equave_disp = '⟨' .. rat.as_ratio(equave) .. '⟩'
if number_of_periods == 1 then
if rat.eq(equave, 2) then
periods_as_text = equave_as_ratio
equave_link = ''
else
equave_disp = ''
local equave_suffix = common_suffix[equave_as_text] or equave_as_text
period_as_text = string.format("1\\%ded%s", number_of_periods, equave_suffix)
end
end
local nL = mos.nL
local period_in_cents = equave_in_cents / number_of_periods
local ns = mos.ns
local has_prev_y = (ns > 1)
local scale_structure = {
local has_prev_x = (nL > 1)
{"Brightest mode", mos.brightest_mode(input_mos)},
local prev_L, next_L, prev_s,next_s, prev_L_prev_s, prev_L_next_s, next_L_prev_s, next_L_next_s = '', '', '', '', '', '', '', ''
{"[[Equave]] (cents)", string.format("%s (%.1f¢)", equave_as_text, equave_in_cents)},
next_s = '[[' .. nL .. 'L ' .. (ns + 1) .. 's' .. equave_link .. '|↓' .. nL .. 'L ' .. (ns + 1) .. 's' .. equave_disp .. ']]'
{"[[Period]] (cents)", string.format("%s (%.1f¢)", period_as_text, period_in_cents)}
next_L = '[[' .. (nL + 1) .. 'L ' .. ns .. 's' .. equave_link .. '|' .. (nL + 1) .. 'L ' .. ns .. 's' .. equave_disp .. '→]]'
}
next_L_next_s = '[[' .. (nL + 1) .. 'L ' .. (ns + 1) .. 's' .. equave_link .. '|' .. (nL + 1) .. 'L ' .. (ns + 1) .. 's' .. equave_disp .. '↘]]'
if has_prev_y then
return scale_structure
prev_s = '[[' .. nL .. 'L ' .. (ns - 1) .. 's' .. equave_link .. '|↑' .. nL .. 'L ' .. (ns - 1) .. 's' .. equave_disp .. ']]'
end
next_L_prev_s = '[[' .. (nL + 1) .. 'L ' .. (ns - 1) .. 's' .. equave_link .. '|' .. (nL + 1) .. 'L ' .. (ns - 1) .. 's' .. equave_disp .. '↗]]'
 
end
-- Helper function
if has_prev_x then
-- Produces generator ranges for scale
prev_L = '[[' .. (nL - 1) .. 'L ' .. ns .. 's' .. equave_link .. '|←' .. (nL - 1) .. 'L ' .. ns .. 's' .. equave_disp .. ']]'
function p.generator_ranges(input_mos)
prev_L_next_s = '[[' .. (nL - 1) .. 'L ' .. (ns + 1) .. 's' .. equave_link .. '|↙' .. (nL - 1) .. 'L ' .. (ns + 1) .. 's' .. equave_disp .. ']]'
local input_mos = input_mos or mos.new(5, 2)
end
if has_prev_y and has_prev_x then
local number_of_periods = utils._gcd(input_mos.nL, input_mos.ns)
prev_L_prev_s = '[[' .. (nL - 1) .. 'L ' .. (ns - 1) .. 's' .. equave_link .. '|↖' .. (nL - 1) .. 'L ' .. (ns - 1) .. 's' .. equave_disp .. ']]'
end
local bright_gen = mos.bright_gen(input_mos)
local dark_gen = {
['L'] = input_mos.nL / number_of_periods - bright_gen['L'],
['s'] = input_mos.ns / number_of_periods - bright_gen['s']
}
local equalized_ed = et.new(input_mos.nL + input_mos.ns, input_mos.equave)
local collapsed_ed = et.new(input_mos.nL, input_mos.equave)
local bright_min_in_steps = et.backslash_display(equalized_ed, bright_gen['L'] + bright_gen['s'])
local bright_max_in_steps = et.backslash_display(collapsed_ed, bright_gen['L'])
local dark_min_in_steps  = et.backslash_display(collapsed_ed, dark_gen['L'])
local dark_max_in_steps  = et.backslash_display(equalized_ed, dark_gen['L'] + dark_gen['s'])
local bright_min_in_cents = et.cents(equalized_ed, bright_gen['L'] + bright_gen['s'])
local bright_max_in_cents = et.cents(collapsed_ed, bright_gen['L'])
local dark_min_in_cents  = et.cents(collapsed_ed, dark_gen['L'])
local dark_max_in_cents  = et.cents(equalized_ed, dark_gen['L'] + dark_gen['s'])
local adjacent_links = {
return {
[1]=prev_L_prev_s,  
{"Bright", string.format("%s (%.1f) to %s (%.1f)", bright_min_in_steps, bright_min_in_cents, bright_max_in_steps, bright_max_in_cents)},
[2]=prev_s,  
{"Dark", string.format("%s (%.1f) to %s (%.1f)", dark_min_in_steps, dark_min_in_cents, dark_max_in_steps, dark_max_in_cents)},
[3]=next_L_prev_s,  
[4]=prev_L,  
[5]=next_L,  
[6]=prev_L_next_s,  
[7]=next_s,  
[8]=next_L_next_s
}
}
end
-- Helper function
-- Produces section entries for tamnams info
function p.tamnams_information(scalesig)
local scalesig = scalesig or "5L 2s"
local tamnams_name = mos.tamnams_name[scalesig] or ""
local tamnams_prefix = mos.tamnams_prefix[scalesig] or ""
local tamnams_abbrev = mos.tamnams_abbrev[scalesig] or ""
local period = rat.as_ratio(equave)
if tamnams_name == "" then
d = gcd(nL, ns)
return nil
if d > 1 then
else
period = ET.backslash_display(ET.new(d, equave), 1)
return {
{"Name", tamnams_name},
{"Prefix", tamnams_prefix},
{"Abbrev.", tamnams_abbrev}
}
end
end
local pattern = MOS.brightest_mode(mos)
end
local collapsed_et = ET.new(nL, equave)
 
local abstract_bright_gen = MOS.bright_gen(mos)
-- Helper function
local collapsed_bright_steps = abstract_bright_gen['L']
-- Produces section for related scales
local collapsed_dark_steps = nL/d - collapsed_bright_steps
function p.related_scales(input_mos)
local equalized_et = ET.new(nL + ns, equave)
local input_mos = input_mos or mos.new(5, 2)
local equalized_bright_steps = abstract_bright_gen['L'] + abstract_bright_gen['s']
local equalized_dark_steps = (nL + ns)/d - equalized_bright_steps
local parent_mos = mos.new(math.min(input_mos.nL, input_mos.ns), input_mos.nL - input_mos.ns, input_mos.equave)
local soft_child_mos = mos.new(input_mos.nL+input_mos.ns, math.max(input_mos.nL, input_mos.ns), input_mos.equave)
local hard_child_mos = mos.new(math.max(input_mos.nL, input_mos.ns), input_mos.nL+input_mos.ns, input_mos.equave)
local sister_mos = mos.new(input_mos.ns, input_mos.nL, input_mos.equave)
local structure_data = {}
local parent_scalesig = string.format("[[%s]]", mos.as_string(parent_mos    ))
local soft_scalesig  = string.format("[[%s]]", mos.as_string(soft_child_mos))
local hard_scalesig  = string.format("[[%s]]", mos.as_string(hard_child_mos))
local sister_scalesig = string.format("[[%s]]", mos.as_string(sister_mos    ))
table.insert(structure_data, {
local is_null_large = string.find(parent_scalesig, "0L")
'Brightest mode',
local is_null_small = string.find(parent_scalesig, "0s")
pattern
if is_null_large or is_null_small then
})
parent_scalesig = "none"
table.insert(structure_data, {
'Period',
period
})
table.insert(structure_data, {
'Range for [[bright]] [[generator]]',
display_range(collapsed_bright_steps/collapsed_et.size, equalized_bright_steps/equalized_et.size,
collapsed_bright_steps .. ET.backslash_modifier(collapsed_et) .. ' (' .. round(ET.cents(collapsed_et, collapsed_bright_steps), 1) .. '¢)',
equalized_bright_steps .. ET.backslash_modifier(equalized_et) .. ' (' .. round(ET.cents(equalized_et, equalized_bright_steps), 1) .. '¢)'
)
})
table.insert(structure_data, {
'Range for [[dark]] [[generator]]',
display_range(collapsed_dark_steps/collapsed_et.size, equalized_dark_steps/equalized_et.size,
collapsed_dark_steps .. ET.backslash_modifier(collapsed_et) .. ' (' .. round(ET.cents(collapsed_et, collapsed_dark_steps), 1) .. '¢)',
equalized_dark_steps .. ET.backslash_modifier(equalized_et) .. ' (' .. round(ET.cents(equalized_et, equalized_dark_steps), 1) .. '¢)'
)
})
local tamnams = MOS.tamnams_name[MOS.as_string(mos)]
if tamnams ~= nil then
table.insert(structure_data, {
'[[TAMNAMS#Mos pattern names|TAMNAMS name]]',
tamnams
})
end
end
local pfx = MOS.tamnams_prefix[MOS.as_string(mos)]
 
if pfx ~= nil then
return {
table.insert(structure_data, {
{"Parent", parent_scalesig},
'[[TAMNAMS#Mos pattern names|TAMNAMS prefix]]',
{"Sister", sister_scalesig},
pfx
{"Daughters", soft_scalesig .. ", " .. hard_scalesig}
})
}
end
end
if nL == ns then
 
parent_mos_link = 'none'
-- Helper function
else
-- Produces simple equal tunings
parent_mos_link = '[[' .. math.min(nL, ns) .. 'L ' .. math.max(nL, ns)-math.min(nL, ns) .. 's' .. equave_link .. '|' 
function p.equal_tunings(input_mos)
.. math.min(nL, ns) .. 'L ' .. math.max(nL, ns)-math.min(nL, ns) .. 's' .. equave_disp .. ']]'
local input_mos = input_mos or mos.new(5, 2)
end
table.insert(structure_data, {
'Parent MOS',
parent_mos_link
})
table.insert(structure_data, {
'[[Operations on MOSes#Sister_MOS|Sister MOS]]',
'[[' .. ns .. 'L ' .. nL .. 's' .. equave_link .. '|' .. ns .. 'L ' .. nL .. 's' .. equave_disp .. ']]'
})
table.insert(structure_data, {
'Daughter MOSes',
'[[' .. nL + ns .. 'L ' .. nL .. 's' .. equave_link .. '|' 
.. nL + ns .. 'L ' .. nL .. 's' .. equave_disp .. ']], [['
.. nL .. 'L ' .. nL + ns .. 's' .. equave_link.. '|' .. nL .. 'L ' .. nL + ns .. 's' .. equave_disp .. ']]'
})
if other_names ~= nil then
table.insert(structure_data, {
'Other names', other_names
})
end
-- Compute et generators by taking mediants from collapsed and equalized generators
local supersoft_et = ET.new(1*collapsed_et.size + 3*equalized_et.size, equave, nil)
local bright_gen = mos.bright_gen(input_mos)
local supersoft_bright_steps = 1*collapsed_bright_steps + 3*equalized_bright_steps
local supersoft_gen = ET.backslash_display(supersoft_et, supersoft_bright_steps)
local supersoft_gen_cents = round(ET.cents(supersoft_et, supersoft_bright_steps), 1)
local soft_et = ET.new(1*collapsed_et.size + 2*equalized_et.size, equave, nil)
local mos_as_vector = {
local soft_bright_steps =  1*collapsed_bright_steps + 2*equalized_bright_steps
['L'] = input_mos.nL,
local soft_gen = ET.backslash_display(soft_et, soft_bright_steps)
['s'] = input_mos.ns
local soft_gen_cents = round(ET.cents(soft_et, soft_bright_steps), 1)
}
local semisoft_et = ET.new(2*collapsed_et.size + 3*equalized_et.size, equave, nil)
local step_ratios = {
local semisoft_bright_steps = 2*collapsed_bright_steps + 3*equalized_bright_steps
{ 1, 1 },
local semisoft_gen = ET.backslash_display(semisoft_et, semisoft_bright_steps)
{ 4, 3 },
local semisoft_gen_cents = round(ET.cents(semisoft_et, semisoft_bright_steps), 1)
{ 3, 2 },
{ 5, 3 },
{ 2, 1 },
{ 5, 2 },
{ 3, 1 },
{ 4, 1 },
{ 1, 0 }
}
local basic_et = ET.new(1*collapsed_et.size + 1*equalized_et.size, equave, nil)
local step_ratio_names = {
local basic_bright_steps = 1*collapsed_bright_steps + 1*equalized_bright_steps
"Equalized",
local basic_gen = ET.backslash_display(basic_et, basic_bright_steps)
"Supersoft",
local basic_gen_cents = round(ET.cents(basic_et, basic_bright_steps), 1)
"Soft",
"Semisoft",
"Basic",
"Semihard",
"Hard",
"Superhard",
"Collapsed"
}
local semihard_et = ET.new(3*collapsed_et.size + 2*equalized_et.size, equave, nil)
local equal_tunings = {}
local semihard_bright_steps = 3*collapsed_bright_steps + 2*equalized_bright_steps
for i = 1, #step_ratios do
local semihard_gen = ET.backslash_display(semihard_et, semihard_bright_steps)
local step_ratio = step_ratios[i]
local semihard_gen_cents = round(ET.cents(semihard_et, semihard_bright_steps), 1)
local ed_size = mos_as_vector['L'] * step_ratio[1] + mos_as_vector['s'] * step_ratio[2]
local gen_size = bright_gen['L'] * step_ratio[1] + bright_gen['s'] * step_ratio[2]
local ed = et.new(ed_size, input_mos.equave)
local ed_as_text = et.as_string(ed)
local gen_in_steps = et.backslash_display(ed, gen_size)
local gen_in_cents = et.cents(ed, gen_size)
local caption = string.format("%s (L:s = %d:%d)", step_ratio_names[i], step_ratio[1], step_ratio[2])
local text = string.format("[[%s | %s]] (%.1f¢)", ed_as_text, gen_in_steps, gen_in_cents)
table.insert(equal_tunings, { caption, text })
end
return equal_tunings
end
 
-- New "main" function
function p._infobox_mos(tuning)
local tuning = tuning or "5L 2s"
local tuning_parsed = mos.parse(tuning)
local hard_et = ET.new(2*collapsed_et.size + 1*equalized_et.size, equave, nil)
local sections = {}
local hard_bright_steps = 2*collapsed_bright_steps + 1*equalized_bright_steps
local hard_gen = ET.backslash_display(hard_et, hard_bright_steps)
local hard_gen_cents = round(ET.cents(hard_et, hard_bright_steps), 1)
local superhard_et = ET.new(3*collapsed_et.size + 1*equalized_et.size, equave, nil)
local scale_structure_header = "Scale structure"
local superhard_bright_steps = 3*collapsed_bright_steps + 1*equalized_bright_steps
local scale_structure_section = p.scale_structure(tuning_parsed)
local superhard_gen = ET.backslash_display(superhard_et, superhard_bright_steps)
table.insert(sections, {scale_structure_header, scale_structure_section})
local superhard_gen_cents = round(ET.cents(superhard_et, superhard_bright_steps), 1)
local et_data = {}
local gen_ranges_headher = "Generator ranges"
table.insert(et_data, {
local gen_ranges_section = p.generator_ranges(tuning_parsed)
'[[TAMNAMS#Step ratio spectrum|Supersoft]] (L:s = 4:3)',
table.insert(sections, {gen_ranges_headher, gen_ranges_section})
'[[' .. ET.as_string(supersoft_et) .. '|' .. supersoft_gen .. ']] (' .. supersoft_gen_cents .. '¢)'
})
table.insert(et_data, {
'[[TAMNAMS#Step ratio spectrum|Soft]] (L:s = 3:2)',
'[[' .. ET.as_string(soft_et) .. '|' .. soft_gen .. ']] (' .. soft_gen_cents .. '¢)'
})
table.insert(et_data, {
'[[TAMNAMS#Step ratio spectrum|Semisoft]] (L:s = 5:3)',
'[[' .. ET.as_string(semisoft_et) .. '|' .. semisoft_gen .. ']] (' .. semisoft_gen_cents .. '¢)'
})
table.insert(et_data, {
'[[TAMNAMS#Step ratio spectrum|Basic]] (L:s = 2:1)',
'[[' .. ET.as_string(basic_et) .. '|' .. basic_gen .. ']] (' .. basic_gen_cents .. '¢)'
})
table.insert(et_data, {
'[[TAMNAMS#Step ratio spectrum|Semihard]] (L:s = 5:2)',
'[[' .. ET.as_string(semihard_et) .. '|' .. semihard_gen .. ']] (' .. semihard_gen_cents .. '¢)'
})
table.insert(et_data, {
'[[TAMNAMS#Step ratio spectrum|Hard]] (L:s = 3:1)',
'[[' .. ET.as_string(hard_et) .. '|' .. hard_gen .. ']] (' .. hard_gen_cents .. '¢)'
})
table.insert(et_data, {
'[[TAMNAMS#Step ratio spectrum|Superhard]] (L:s = 4:1)',
'[[' .. ET.as_string(superhard_et) .. '|' .. superhard_gen .. ']] (' .. superhard_gen_cents .. '¢)'
})
-- Create xenpaper links
local tamnams_info_header = "TAMNAMS information"
local step_ratios = { {4,3}, {3,2}, {5,3}, {2,1}, {5,2}, {3,1}, {4,1} }
local tamnams_info_section = p.tamnams_information(tuning)
local step_ratio_names = { "Supersoft", "Soft", "Semisoft", "Basic", "Semihard", "Hard", "Superhard" }
if tamnams_info_section ~= nil then
local xenpaper_links = {}
table.insert(sections, {tamnams_info_header, tamnams_info_section})
for i = 1, #step_ratios do
local link = "[" .. xp.mosstep_pattern_to_xenpaper_link(pattern, step_ratios[i], mos.equave) .. " " .. step_ratio_names[i] .. "]"
table.insert(xenpaper_links, link)
end
end
local related_scales_header = "Related scales"
local related_scales_section = p.related_scales(tuning_parsed)
table.insert(sections, {related_scales_header, related_scales_section})
local equal_tunings_header = "Equal tunings"
local equal_tunings_section = p.equal_tunings(tuning_parsed)
table.insert(sections, {equal_tunings_header, equal_tunings_section})


local title = nL .. 'L ' .. ns .. 's' .. equave_disp
local adjacent_links = p.adjacent_links(tuning_parsed)
result = p.build(
title,
structure_data,
et_data,
adjacent_links,
xenpaper_links
)
return result
return infobox.build_multisection(tuning, sections, adjacent_links)
end
end


Line 343: Line 308:
local debug_mode = tonumber(frame.args['debug']) == 1
local debug_mode = tonumber(frame.args['debug']) == 1
local result = p._infobox_MOS_old(tuning, other_names)
local result = p._infobox_MOS(tuning)
if not debug_mode then
if not debug_mode then
result = result .. p.categorize(tuning)
result = result .. p.categorize(tuning)
Line 350: Line 315:
return result
return result


end
function p.build(title, structure_entries, et_entries, adjacent_links, xenpaper_links)
local s = '<div style="\n' ..
'border: 1px solid #999;\n' ..
'margin: 0;\n' ..
'margin-left: 1em;\n' ..
'margin-bottom: 0.5em;\n' ..
'padding: 0.5em;\n' ..
'background-color: #f0f0f0;\n' ..
'min-width: 15em;\n' ..
'float: right;\n' ..
'max-width: 100%;\n' ..
'overflow: auto;\n' ..
'">\n' ..
'{| width="100%" style="border-collapse: collapse;"\n' ..
'|+ style="font-weight: bold; text-align: center;" | '
s = s .. '<table style="width: 100%; margin: 0"><tr>'
.. '<td style="width: 15%; text-align: left; white-space: nowrap; font-size: smaller">'
.. adjacent_links[1]
.. '</td>'
.. '<td style="\n'
.. 'width: 50%; padding-left: 1em; padding-right: 1em; text-align: center; font-size: smaller">'
.. adjacent_links[2]
.. '\n</td><td style="width: 15%; text-align: right; white-space: nowrap; font-size: smaller">'
.. adjacent_links[3]
.. '</td>'
.. '\n<tr>'
.. '<td style="width: 15%; text-align: left; white-space: nowrap; font-size: smaller">'
.. adjacent_links[4]
.. '</td>'
.. '<td style="width: 50%; padding-left: 1em; padding-right: 1em; text-align: center">'
.. title
.. '</td>'
.. '<td style="width: 15%; text-align: right; white-space: nowrap; font-size: smaller">'
.. adjacent_links[5]
.. '</td>'
.. '</tr>'
.. '<tr>'
.. '<td style="width: 15%; text-align: left; white-space: nowrap; font-size: smaller">'
.. adjacent_links[6]
.. '</td><td style="\n'
.. 'width: 50%; padding-left: 1em; padding-right: 1em; text-align: center; font-size: smaller">'
.. adjacent_links[7]
.. '\n</td>'
.. '<td style="width: 15%; text-align: right; white-space: nowrap; font-size: smaller">'
.. adjacent_links[8]
.. '</td>'
.. '</tr>'
..  '</table>'
s = s .. '\n'
for i, entry in ipairs(structure_entries) do
if #entry > 1 then
local caption = entry[1]
local text = entry[2]
s = s .. '|-\n' ..
'| style="text-align:right; padding-right: 0.25em" | ' .. caption .. '\n' ..
'| style="background-color: white; padding-left: 0.25em; font-weight: bold" | ' .. text .. '\n'
elseif #entry == 1 then
local text = entry[1]
s = s .. '|-\n'
.. '| colspan="2" style="text-align: center;" | ' .. text .. '\n'
end
end
s = s .. '|-\n'
    .. '|colspan="2" style="text-align:center;"| <b>Equal tunings</b>\n'
for i, entry in ipairs(et_entries) do
if #entry > 1 then
local caption = entry[1]
local text = entry[2]
s = s .. '|-\n' ..
'| style="text-align:right; padding-right: 0.25em" | ' .. caption .. '\n' ..
'| style="background-color: white; padding-left: 0.25em; font-weight: bold" | ' .. text .. '\n'
elseif #entry == 1 then
local text = entry[1]
s = s .. '|-\n'
.. '| colspan="2" style="text-align: center;" | ' .. text .. '\n'
end
end
s = s .. '|-\n'
    .. '|colspan="2" style="text-align:center;"| <small>Brightest-mode tunings on \'\'xenpaper\'\'</small>\n'
    .. '|-\n'
s = s .. '| colspan="2" style="text-align:center;"|<small>'
for i = 1, #xenpaper_links do
s = s .. xenpaper_links[i] .. " "
end
s = s .. "</small>\n"
s = s .. '|}</div>'
return s
end
end


function p.tester()
function p.tester()


local adjacent_links = { "4L 1s", "5L 1s", "6L 1s", "4L 2s", "6L 2s", "4L 3s", "5L 3s", "6L 3s" }
local adjacent_links = { "4L 1s", "5L 1s", "6L 1s", "4L 2s", "6L 2s", "4L 3s", "5L 3s", "" }
local section_1_entries = {
local section_1_entries = {

Revision as of 10:42, 12 April 2024

Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:Infobox MOS.

This module generates an infobox providing information about a given moment of symmetry (MOS) scale.

Introspection summary for Module:Infobox MOS 
Functions provided (10)
Line Function Params
25 categorize (tuning)
72 adjacent_links (input_mos)
107 scale_structure (input_mos)
134 generator_ranges (input_mos)
166 tamnams_information (scalesig)
186 related_scales (input_mos)
214 equal_tunings (input_mos)
270 _infobox_mos (main) (tuning)
304 infobox_MOS (invokable) (frame)
319 tester none
Lua modules required (5)
Variable Module Functions used
et Module:ET new
backslash_display
cents
as_string
infobox Module:Infobox build_multisection
mos Module:MOS parse
find_ancestor
as_string
new
brightest_mode
bright_gen
rat Module:Rational new
eq
as_ratio
cents
utils Module:Utils _gcd

No function descriptions were provided. The Lua code may have further information.


local p = {}
local utils = require('Module:Utils')
local rat = require('Module:Rational')
local mos = require('Module:MOS')
local et = require('Module:ET')
--local xp = require('Module:Xenpaper')		-- No xenpaper links for now
local infobox = require('Module:Infobox')

local common_suffix = {
	['3/2'] = 'f',
	['2'] = 'o',
	['2/1'] = 'o',
	['3'] = 't',
	['3/1'] = 't',
}

local common_ratio = {
	['f'] = rat.new(3, 2),
	['o'] = 2,
	['t'] = 3,
}

-- Helper function
-- Adds categories
function p.categorize(tuning)
	local tuning = tuning or "5L 2s"
	local input_mos = mos.parse(tuning)
	
	-- Add to category of abstact mosses
	local categories = "[[Category:Abstract MOS patterns]]"
	
	-- Add tuning category
	--categories = categories .. string.format('[[Category:%s]]', tuning)
	
	-- Add notecount category if the notecount is greater than 3
	local notecount = input_mos.nL + input_mos.ns
	if notecount > 3 then
		categories = categories .. string.format('[[Category:%d-tone scales]]', notecount)
	end
	
	-- If the mos is octave-equivalent, add appropriate tamnams-named categories
	-- Otherwise, add to nonoctave category
	if rat.eq(input_mos.equave, rat.new(2)) then
		-- Caveats:
		-- - Only octave-equivalent mos names are used as categories.
		-- - Monowood and biwood are excluded (for now).
		-- - Mosses whose notecounts > 10 and periods < 5 are categorized under
		--   the closest tamnams-named ancestor.
		-- - Monolarge scales aren't categorized for they're all related to one
		--   another. (For now.)
		-- - Monosmall descendants that descend from 1L 9s aren't categorized
		--   for now.
		local ancestor_mos = mos.find_ancestor(input_mos)
		local tamnams_name = mos.tamnams_name[mos.as_string(ancestor_mos)]
		
		if tamnams_name == "arch(a)eotonic" then
			tamnams_name = "archaeotonic"
		end
		
		if tamnams_name ~= nil then
			categories = categories .. string.format('[[Category:%s]]', tamnams_name)
		end
	else
		categories = categories .. '[[Category:Nonoctave]]'
	end
	
	return categories
end

-- Helper function
-- Creates adjacent links for mos, found by +/-1 large or +/- small steps
function p.adjacent_links(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	local long_equave_as_text = ""
	local equave_as_text = ""
	if not rat.eq(input_mos.equave, 2) then
		long_equave_as_text = string.format(" (%s-equivalent)", rat.as_ratio(input_mos.equave))
		equave_as_text = string.format("⟨%s⟩)", rat.as_ratio(input_mos.equave))
	end
	
	local adjacent_links = {
		string.format("[[%dL %ds%s | ↖%dL %ds%s]]", input_mos.nL-1, input_mos.ns-1, long_equave_as_text, input_mos.nL-1, input_mos.ns-1, equave_as_text),
		string.format("[[%dL %ds%s | ↑%dL %ds%s]]", input_mos.nL  , input_mos.ns-1, long_equave_as_text, input_mos.nL  , input_mos.ns-1, equave_as_text),
		string.format("[[%dL %ds%s | %dL %ds%s↗]]", input_mos.nL+1, input_mos.ns-1, long_equave_as_text, input_mos.nL+1, input_mos.ns-1, equave_as_text),
		string.format("[[%dL %ds%s | ←%dL %ds%s]]", input_mos.nL-1, input_mos.ns  , long_equave_as_text, input_mos.nL-1, input_mos.ns  , equave_as_text),
		string.format("[[%dL %ds%s | %dL %ds%s→]]", input_mos.nL+1, input_mos.ns  , long_equave_as_text, input_mos.nL+1, input_mos.ns  , equave_as_text),
		string.format("[[%dL %ds%s | ↙%dL %ds%s]]", input_mos.nL-1, input_mos.ns+1, long_equave_as_text, input_mos.nL-1, input_mos.ns+1, equave_as_text),
		string.format("[[%dL %ds%s | ↓%dL %ds%s]]", input_mos.nL  , input_mos.ns+1, long_equave_as_text, input_mos.nL  , input_mos.ns+1, equave_as_text),
		string.format("[[%dL %ds%s | %dL %ds%s↘]]", input_mos.nL+1, input_mos.ns+1, long_equave_as_text, input_mos.nL+1, input_mos.ns+1, equave_as_text),
	}
	
	for i = 1, #adjacent_links do
		local is_null_large = string.find(adjacent_links[i], "0L")
		local is_null_small = string.find(adjacent_links[i], "0s")
		
		if is_null_large or is_null_small then
			adjacent_links[i] = ""
		end
	end
	
	return adjacent_links
end

-- Helper function
-- Produces section entries for scale sturcture
function p.scale_structure(input_mos)
	local input_mos = input_mos or mos.new(5, 5, 3)
	
	local equave_as_text = rat.as_ratio(input_mos.equave)
	local equave_in_cents = rat.cents(input_mos.equave)
	
	local number_of_periods = utils._gcd(input_mos.nL, input_mos.ns)
	local period_as_text = ""
	if number_of_periods == 1 then
		periods_as_text = equave_as_ratio
	else
		local equave_suffix = common_suffix[equave_as_text] or equave_as_text
		period_as_text = string.format("1\\%ded%s", number_of_periods, equave_suffix)
	end
	local period_in_cents = equave_in_cents / number_of_periods
	
	local scale_structure = {
		{"Brightest mode", mos.brightest_mode(input_mos)},
		{"[[Equave]] (cents)", string.format("%s (%.1f¢)", equave_as_text, equave_in_cents)},
		{"[[Period]] (cents)", string.format("%s (%.1f¢)", period_as_text, period_in_cents)}
	}
	
	return scale_structure
end

-- Helper function
-- Produces generator ranges for scale
function p.generator_ranges(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	local number_of_periods = utils._gcd(input_mos.nL, input_mos.ns)
	
	local bright_gen = mos.bright_gen(input_mos)
	local dark_gen = {
		['L'] = input_mos.nL / number_of_periods - bright_gen['L'],
		['s'] = input_mos.ns / number_of_periods - bright_gen['s']
	}
	
	local equalized_ed = et.new(input_mos.nL + input_mos.ns, input_mos.equave)
	local collapsed_ed = et.new(input_mos.nL, input_mos.equave)
	
	local bright_min_in_steps = et.backslash_display(equalized_ed, bright_gen['L'] + bright_gen['s'])
	local bright_max_in_steps = et.backslash_display(collapsed_ed, bright_gen['L'])
	local dark_min_in_steps   = et.backslash_display(collapsed_ed, dark_gen['L'])
	local dark_max_in_steps   = et.backslash_display(equalized_ed, dark_gen['L'] + dark_gen['s'])
	
	local bright_min_in_cents = et.cents(equalized_ed, bright_gen['L'] + bright_gen['s'])
	local bright_max_in_cents = et.cents(collapsed_ed, bright_gen['L'])
	local dark_min_in_cents   = et.cents(collapsed_ed, dark_gen['L'])
	local dark_max_in_cents   = et.cents(equalized_ed, dark_gen['L'] + dark_gen['s'])
	
	return {
		{"Bright", string.format("%s (%.1f) to %s (%.1f)", bright_min_in_steps, bright_min_in_cents, bright_max_in_steps, bright_max_in_cents)},
		{"Dark", string.format("%s (%.1f) to %s (%.1f)", dark_min_in_steps, dark_min_in_cents, dark_max_in_steps, dark_max_in_cents)},
	}
end

-- Helper function
-- Produces section entries for tamnams info
function p.tamnams_information(scalesig)
	local scalesig = scalesig or "5L 2s"
	
	local tamnams_name = mos.tamnams_name[scalesig] or ""
	local tamnams_prefix = mos.tamnams_prefix[scalesig] or ""
	local tamnams_abbrev = mos.tamnams_abbrev[scalesig] or ""
	
	if tamnams_name == "" then
		return nil
	else
		return {
			{"Name", tamnams_name},
			{"Prefix", tamnams_prefix},
			{"Abbrev.", tamnams_abbrev}
		}
	end
end

-- Helper function
-- Produces section for related scales
function p.related_scales(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	local parent_mos = mos.new(math.min(input_mos.nL, input_mos.ns), input_mos.nL - input_mos.ns, input_mos.equave)
	local soft_child_mos = mos.new(input_mos.nL+input_mos.ns, math.max(input_mos.nL, input_mos.ns), input_mos.equave)
	local hard_child_mos = mos.new(math.max(input_mos.nL, input_mos.ns), input_mos.nL+input_mos.ns, input_mos.equave)
	local sister_mos = mos.new(input_mos.ns, input_mos.nL, input_mos.equave)
	
	local parent_scalesig = string.format("[[%s]]", mos.as_string(parent_mos    ))
	local soft_scalesig   = string.format("[[%s]]", mos.as_string(soft_child_mos))
	local hard_scalesig   = string.format("[[%s]]", mos.as_string(hard_child_mos))
	local sister_scalesig = string.format("[[%s]]", mos.as_string(sister_mos    ))
	
	local is_null_large = string.find(parent_scalesig, "0L")
	local is_null_small = string.find(parent_scalesig, "0s")
	if is_null_large or is_null_small then
		parent_scalesig = "none"
	end

	return {
		{"Parent", parent_scalesig},
		{"Sister", sister_scalesig},
		{"Daughters", soft_scalesig .. ", " .. hard_scalesig}
	}
end

-- Helper function
-- Produces simple equal tunings
function p.equal_tunings(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	local bright_gen = mos.bright_gen(input_mos)
	
	local mos_as_vector = {
		['L'] = input_mos.nL,
		['s'] = input_mos.ns
	}
	
	local step_ratios = {
		{ 1, 1 },
		{ 4, 3 },
		{ 3, 2 },
		{ 5, 3 },
		{ 2, 1 },
		{ 5, 2 },
		{ 3, 1 },
		{ 4, 1 },
		{ 1, 0 }
	}
	
	local step_ratio_names = {
		"Equalized",
		"Supersoft",
		"Soft",
		"Semisoft",
		"Basic",
		"Semihard",
		"Hard",
		"Superhard",
		"Collapsed"
	}
	
	local equal_tunings = {}
	for i = 1, #step_ratios do
		local step_ratio = step_ratios[i]
		
		local ed_size = mos_as_vector['L'] * step_ratio[1] + mos_as_vector['s'] * step_ratio[2]
		local gen_size = bright_gen['L'] * step_ratio[1] + bright_gen['s'] * step_ratio[2]
		
		local ed = et.new(ed_size, input_mos.equave)
		local ed_as_text = et.as_string(ed)
		
		local gen_in_steps = et.backslash_display(ed, gen_size)
		local gen_in_cents = et.cents(ed, gen_size)
		
		local caption = string.format("%s (L:s = %d:%d)", step_ratio_names[i], step_ratio[1], step_ratio[2])
		local text = string.format("[[%s | %s]] (%.1f¢)", ed_as_text, gen_in_steps, gen_in_cents)
		
		table.insert(equal_tunings, { caption, text })
	end
	return equal_tunings
end

-- New "main" function
function p._infobox_mos(tuning)
	local tuning = tuning or "5L 2s"
	local tuning_parsed = mos.parse(tuning)
	
	local sections = {}
	
	local scale_structure_header = "Scale structure"
	local scale_structure_section = p.scale_structure(tuning_parsed)
	table.insert(sections, {scale_structure_header, scale_structure_section})
	
	local gen_ranges_headher = "Generator ranges"
	local gen_ranges_section = p.generator_ranges(tuning_parsed)
	table.insert(sections, {gen_ranges_headher, gen_ranges_section})
	
	local tamnams_info_header = "TAMNAMS information"
	local tamnams_info_section = p.tamnams_information(tuning)
	if tamnams_info_section ~= nil then
		table.insert(sections, {tamnams_info_header, tamnams_info_section})
	end
	
	local related_scales_header = "Related scales"
	local related_scales_section = p.related_scales(tuning_parsed)
	table.insert(sections, {related_scales_header, related_scales_section})
	
	local equal_tunings_header = "Equal tunings"
	local equal_tunings_section = p.equal_tunings(tuning_parsed)
	table.insert(sections, {equal_tunings_header, equal_tunings_section})

	local adjacent_links = p.adjacent_links(tuning_parsed)
	
	return infobox.build_multisection(tuning, sections, adjacent_links)
end

-- Wrapper function
function p.infobox_MOS(frame)
	
	local tuning = frame.args['Tuning']
	local other_names = frame.args['Other names'] or nil
	local debug_mode = tonumber(frame.args['debug']) == 1
	
	local result = p._infobox_MOS(tuning)
	if not debug_mode then
		result = result .. p.categorize(tuning)
	end
	
	return result

end

function p.tester()

	local adjacent_links = { "4L 1s", "5L 1s", "6L 1s", "4L 2s", "6L 2s", "4L 3s", "5L 3s", "" }
	
	local section_1_entries = {
		{"Brightest mode", "LLLsLLs"},
		{"Period (cents)", "2\1 (1200¢)"},
		{"Bright generator range", "4\7 (685.7¢) to 3\5 (720¢)"},
		{"Dark generator range", "2\5 (480¢) to 3\7 (514.3¢)"}
	}
	
	local section_2_entries = {
		{"Name", "diatonic"},
		{"Prefix", "dia-"},
		{"Abbrev.", "dia."}
	}
	
	local section_3_entries = {
		{"Parent MOS", "2L 3s"},
		{"Sister MOS", "2L 5s"},
		{"Daughter MOSes", "7L 5s, 5L 7s"}
	}
	
	local section_4_entries = {
		{"Supersoft (L:s = 4:3)", "15\\26 (692.3¢)"},
		{"Soft (L:s = 3:2)", "11\\19 (694.7¢)"},
		{"Semisoft (L:s = 5:3)", "18\\31 (696.8¢)"},
		{"Basic (L:s = 2:1)", "7\\12 (700¢)"},
		{"Semihard (L:s = 5:2)", "17\\29 (703.4¢)"},
		{"Hard (L:s = 3:1)", "10\\17 (705.9¢)"},
		{"Superhard (L:s = 4:1)", "13\\22 (709.1¢)"},
	}
	
	local sections = {
		{"Scale structure", section_1_entries},
		{"TAMNAMS information", section_2_entries},
		{"Related scales", section_3_entries},
		{"Simple equal tunings", section_4_entries},
	}
	
	return infobox.build_multisection("5L 2s (2/1-equivalent)", sections, adjacent_links)
	
end

return p