Module:Infobox MOS: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
m Rewording
Ganaram inukshuk (talk | contribs)
m An s
Line 372: Line 372:
{"[[Operations_on_MOSes#Sister_MOS | Sister]]", sister_scalesig},
{"[[Operations_on_MOSes#Sister_MOS | Sister]]", sister_scalesig},
{"[[Operations_on_MOSes#Parent_MOS | Parent]] (subset)", parent_scalesig},
{"[[Operations_on_MOSes#Parent_MOS | Parent]] (subset)", parent_scalesig},
{"[[Operations_on_MOSes#Daughter_MOS | Daughters]] (superset)", soft_scalesig .. ", " .. hard_scalesig}
{"[[Operations_on_MOSes#Daughter_MOS | Daughters]] (supersets)", soft_scalesig .. ", " .. hard_scalesig}
}
}

Revision as of 19:09, 13 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 (13)
Line Function Params
32 concatenate_tables (t1, t2)
40 categorize (tuning)
80 adjacent_links (input_mos)
121 scale_structure (input_mos)
154 generator_sizes (input_mos)
201 step_sizes (input_mos)
243 find_mos_ancestor (input_mos)
295 tamnams_information (input_mos)
351 related_scales (input_mos)
386 equal_tunings (input_mos)
457 _infobox_mos (main) (tuning)
497 infobox_MOS (invokable) (frame)
512 tester none
Lua modules required (5)
Variable Module Functions used
et Module:ET new
backslash_display
cents
as_string
infobox Module:Infobox build_multilink
mos Module:MOS parse
find_ancestor
as_string
new
brightest_mode
bright_gen
as_long_string
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')

-- TODO:
-- (For infobox module): rework multisection infobox to only multilink; existing code already works with multiple sections
-- Add back "other names" section
-- Add back xenpaper links

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
-- Concatenates the contents of two tables into one
-- This doesn't have a return value; rather, the first table passed has the
-- second table's contents added to it.
function p.concatenate_tables(t1, t2)
	for i=1, #t2 do
		t1[#t1+1] = t2[i]
	end
end

-- 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 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.
		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 equave_as_long_string = ""
	local equave_as_string = ""
	if not rat.eq(input_mos.equave, 2) then
		equave_as_long_string = string.format(" (%s-equivalent)", rat.as_ratio(input_mos.equave))
		equave_as_string = 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, equave_as_long_string, input_mos.nL-1, input_mos.ns-1, equave_as_string),
		string.format("[[%dL %ds%s | ↑%dL %ds%s]]", input_mos.nL  , input_mos.ns-1, equave_as_long_string, input_mos.nL  , input_mos.ns-1, equave_as_string),
		string.format("[[%dL %ds%s | %dL %ds%s↗]]", input_mos.nL+1, input_mos.ns-1, equave_as_long_string, input_mos.nL+1, input_mos.ns-1, equave_as_string),
		string.format("[[%dL %ds%s | ←%dL %ds%s]]", input_mos.nL-1, input_mos.ns  , equave_as_long_string, input_mos.nL-1, input_mos.ns  , equave_as_string),
		string.format("[[%dL %ds%s | %dL %ds%s→]]", input_mos.nL+1, input_mos.ns  , equave_as_long_string, input_mos.nL+1, input_mos.ns  , equave_as_string),
		string.format("[[%dL %ds%s | ↙%dL %ds%s]]", input_mos.nL-1, input_mos.ns+1, equave_as_long_string, input_mos.nL-1, input_mos.ns+1, equave_as_string),
		string.format("[[%dL %ds%s | ↓%dL %ds%s]]", input_mos.nL  , input_mos.ns+1, equave_as_long_string, input_mos.nL  , input_mos.ns+1, equave_as_string),
		string.format("[[%dL %ds%s | %dL %ds%s↘]]", input_mos.nL+1, input_mos.ns+1, equave_as_long_string, input_mos.nL+1, input_mos.ns+1, equave_as_string),
	}
	
	-- Links that contain either "0L" or "0s" when the parent's L/s-count is 1
	-- refer to degenerate mosses (null-large and null-small, or 0L/0s) and
	-- links for those should be made blank instead.
	for i = 1, #adjacent_links do
		local number_of_periods = utils._gcd(input_mos.nL, input_mos.ns)
		local is_null_large = string.find(adjacent_links[i], "0L") and input_mos.nL == 1
		local is_null_small = string.find(adjacent_links[i], "0s") and input_mos.ns == 1
		
		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
-- Section is returned as a jagged array and return value must be merged into
-- a larger array.
function p.scale_structure(input_mos)
	local input_mos = input_mos or mos.new(5, 5, 3)
	
	local equave_as_string = 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_string = ""
	if number_of_periods == 1 then
		period_as_string = equave_as_string
	else
		local ed = et.new(number_of_periods, input_mos.equave)
		period_as_string = et.backslash_display(ed, 1)
	end
	local period_in_cents = equave_in_cents / number_of_periods
	
	local section_entries = {
		{"Brightest mode", mos.brightest_mode(input_mos)},
		{"[[Equave]]", string.format("%s (%.1f¢)", equave_as_string, equave_in_cents)},
		{"[[Period]]", string.format("%s (%.1f¢)", period_as_string, period_in_cents)}
	}
	
	local section_header = "Scale structure"
	
	local section = { {string.format("<b>%s</b>", section_header)} }
	p.concatenate_tables(section, section_entries)
	return section
end

-- Helper function
-- Produces generator ranges for scale
-- Section is returned as a jagged array and return value must be merged into
-- a larger array.
function p.generator_sizes(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'])
	
	local section_header = "Generator ranges"
	if rat.eq(input_mos.equave, 3) then
		section_header = section_header .. " (in steps of edt)"
	elseif rat.eq(input_mos.equave, rat.new(3,2)) then
		section_header = section_header .. " (in steps of edf)"
	elseif not rat.eq(input_mos.equave, 2) then
		section_header = string.format("%s (in steps of ed%s)", section_header, rat.as_ratio(input_mos.equave))
	end
	
	local section_entries = {
		{"[[Bright]]", string.format("%s to %s (%.1f¢ to %.1f¢)", bright_min_in_steps, bright_max_in_steps, bright_min_in_cents, bright_max_in_cents)},
		{"[[Dark]]", string.format("%s to %s (%.1f¢ to %.1f¢)", dark_min_in_steps, dark_max_in_steps, dark_min_in_cents, dark_max_in_cents)},
	}
	
	local section = { {string.format("<b>%s</b>", section_header)} }
	p.concatenate_tables(section, section_entries)
	return section
end

-- Helper function
-- Produces ranges small and large steps
-- Section is returned as a jagged array and return value must be merged into
-- a larger array.
function p.step_sizes(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	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, "")
	
	-- Large and small steps
	-- The small step lower bound is basically zero (when collapsed).
	-- The upper bound for a small step and the lower bound for a large step
	-- are the same.
	-- The upper bound of a large step is when the step ratio is collapsed.
	local small_step_min_in_steps = et.backslash_display(collapsed_ed, 0)
	local large_step_min_in_steps = et.backslash_display(equalized_ed, 1)
	local large_step_max_in_steps = et.backslash_display(collapsed_ed, 1)
	
	local small_step_min_in_cents = et.cents(collapsed_ed, 0)
	local large_step_min_in_cents = et.cents(equalized_ed, 1)
	local large_step_max_in_cents = et.cents(collapsed_ed, 1)
	
	local section_header = "Step size ranges"
	if rat.eq(input_mos.equave, 3) then
		section_header = section_header .. " (in steps of edt)"
	elseif rat.eq(input_mos.equave, rat.new(3,2)) then
		section_header = section_header .. " (in steps of edf)"
	elseif not rat.eq(input_mos.equave, 2) then
		section_header = string.format("%s (in steps of ed%s)", section_header, rat.as_ratio(input_mos.equave))
	end
	
	local section_entries = {
		{"Large (L)", string.format("%s to %s (%.1f¢ to %.1f¢)", large_step_min_in_steps, large_step_max_in_steps, large_step_min_in_cents, large_step_max_in_cents)},
		{"Small (s)", string.format("%s to %s (%.1f¢ to %.1f¢)", small_step_min_in_steps, large_step_min_in_steps, small_step_min_in_cents, large_step_min_in_cents)}
	}
	
	local section = { {string.format("<b>%s</b>", section_header)} }
	p.concatenate_tables(section, section_entries)
	return section
end

-- Helper function for a helper function
-- Determines what mos the given mos descends from
-- as well as what step ratio that produces this scale
-- Mosses within the "named range" passed here will return itself
function p.find_mos_ancestor(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	
	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
-- Produces section entries for tamnams info
-- Conditions for tamnams info inclusion:
-- - Scale is octave-equivalent.
-- - Scales within the "named range" (6-10 notes, or is 1L 1s or 2L 2s) have
--   a tamnams name.
-- - Scales with a notecount greater than 10 and no more than 5 periods have
--   a tamnams-named ancestor.
-- - Scales with a notecount greater than 10 and more than 5 periods don't have
--   a tamnams-named ancestor, but will report what nL ns mos they descend from.
-- Section is returned as a jagged array and return value must be merged into
-- a larger array.
function p.tamnams_information(input_mos)
	local input_mos = input_mos or mos.new(5, 2)
	local scalesig = string.format("%dL %ds", input_mos.nL, input_mos.ns)
	
	local notecount = input_mos.nL + input_mos.ns
	local number_of_periods = utils._gcd(input_mos.nL, input_mos.ns)
	
	local tamnams_name = mos.tamnams_name[scalesig]
	local tamnams_prefix = mos.tamnams_prefix[scalesig]
	local tamnams_abbrev = mos.tamnams_abbrev[scalesig]
	
	local is_octave_equivalent = rat.eq(input_mos.equave, 2)
	local is_within_named_range = tamnams_name ~= nil and notecount <= 10
	local is_root_mos = input_mos.nL == input_mos.ns
	
	local section_header = "TAMNAMS information"
	local section_entries = nil
	if is_octave_equivalent then
		if is_within_named_range then
			-- Mos has a tamnams name
			section_entries = {
				{"[[TAMNAMS#Mos_pattern_names | Name]]", tamnams_name},
				{"[[TAMNAMS#Mos_pattern_names | Prefix]]", tamnams_prefix},
				{"[[TAMNAMS#Mos_pattern_names | Abbrev.]]", tamnams_abbrev}
			}
		elseif not is_within_named_range and notecount > 10 and not is_root_mos then
			-- Mos (probably) has a tamnams-named ancestor
			local ancestor_mos, lg_chunk, sm_chunk, generations = p.find_mos_ancestor(input_mos)
			local ancestor_scalesig = mos.as_string(ancestor_mos)
			local ancestor_long_scalesig = mos.as_long_string(ancestor_mos)
			local ancestor_name = mos.tamnams_name[ancestor_scalesig]
			
			local ancestor_entry = string.format("[[%s | %s]]", ancestor_long_scalesig, ancestor_scalesig)
			if ancestor_name ~= nil then
				ancestor_entry = ancestor_entry .. string.format(" (%s)", ancestor_name)
			end
			
			section_entries = {
				{"Descends from", ancestor_entry}
			}
		end
	end
	
	if section_entries ~= nil then
		local section = { {string.format("<b>%s</b>", section_header)} }
		p.concatenate_tables(section, section_entries)
		return section
	else
		return nil
	end
end

-- Helper function
-- Produces section for related scales
-- Section is returned as a jagged array and return value must be merged into
-- a larger array.
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), math.abs(input_mos.nL-input_mos.ns), input_mos.equave)
	local sister_mos = mos.new(input_mos.ns, input_mos.nL, input_mos.equave)
	local soft_child_mos = mos.new(input_mos.nL+input_mos.ns, input_mos.nL, input_mos.equave)
	local hard_child_mos = mos.new(input_mos.nL, input_mos.nL+input_mos.ns, input_mos.equave)
	
	local parent_scalesig = string.format("[[%s | %s]]", mos.as_long_string(parent_mos    ), mos.as_string(parent_mos    ))
	local sister_scalesig = string.format("[[%s | %s]]", mos.as_long_string(sister_mos    ), mos.as_string(sister_mos    ))
	local soft_scalesig   = string.format("[[%s | %s]]", mos.as_long_string(soft_child_mos), mos.as_string(soft_child_mos))
	local hard_scalesig   = string.format("[[%s | %s]]", mos.as_long_string(hard_child_mos), mos.as_string(hard_child_mos))
	
	local number_of_periods = utils._gcd(input_mos.nL, input_mos.ns)
	local is_null_large = string.find(parent_scalesig, "0L") and input_mos.nL == number_of_periods
	local is_null_small = string.find(parent_scalesig, "0s") and input_mos.ns == number_of_periods
	if is_null_large or is_null_small then
		parent_scalesig = "none"
	end

	local section_entries = {
		{"[[Operations_on_MOSes#Sister_MOS | Sister]]", sister_scalesig},
		{"[[Operations_on_MOSes#Parent_MOS | Parent]] (subset)", parent_scalesig},
		{"[[Operations_on_MOSes#Daughter_MOS | Daughters]] (supersets)", soft_scalesig .. ", " .. hard_scalesig}
	}
	
	local section_header = "Related scales"
	
	local section = { {string.format("<b>%s</b>", section_header)} }
	p.concatenate_tables(section, section_entries)
	return section
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 section_entries = {}
	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_string = et.as_string(ed)
		
		local ed_no_prefix = et.new(ed_size, input_mos.equave, "")
		local ed_as_string_no_prefix = et.as_string(ed_no_prefix)
		
		local gen_in_steps = et.backslash_display(ed_no_prefix, 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_string, gen_in_steps, gen_in_cents)
		
		table.insert(section_entries, { caption, text })
	end
	
	local section_header = "Equal tunings"
	if rat.eq(input_mos.equave, 3) then
		section_header = section_header .. " (in steps of edt)"
	elseif rat.eq(input_mos.equave, rat.new(3,2)) then
		section_header = section_header .. " (in steps of edf)"
	elseif not rat.eq(input_mos.equave, 2) then
		section_header = string.format("%s (in steps of ed%s)", section_header, rat.as_ratio(input_mos.equave))
	end
	
	local section = { {string.format("<b>%s</b>", section_header)} }
	p.concatenate_tables(section, section_entries)
	return section
end

-- New "main" function
function p._infobox_mos(tuning)
	local tuning = tuning or "5L 2s"
	local tuning_parsed = mos.parse(tuning)
	
	local sections = {}
	
	-- Scale structure section
	local scale_structure = p.scale_structure(tuning_parsed)
	p.concatenate_tables(sections, scale_structure)
	
	-- Interval range section
	local step_sizes = p.step_sizes(tuning_parsed)
	p.concatenate_tables(sections, step_sizes)
	
	-- Generator sizes
	local gen_sizes = p.generator_sizes(tuning_parsed)
	p.concatenate_tables(sections, gen_sizes)
	
	-- Tamnams info section, if applicable
	local tamnams_info = p.tamnams_information(tuning_parsed)
	if tamnams_info ~= nil then
		p.concatenate_tables(sections, tamnams_info)
	end
	
	-- Related scales section
	local related_scales = p.related_scales(tuning_parsed)
	p.concatenate_tables(sections, related_scales)
	
	-- Equal tunings section
	local equal_tunings = p.equal_tunings(tuning_parsed)
	p.concatenate_tables(sections, equal_tunings)

	-- Adjacent links
	local adjacent_links = p.adjacent_links(tuning_parsed)
	
	return infobox.build_multilink(tuning, sections, adjacent_links)
	--return sections
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 prev_link = "5L 1s"
	local next_link = "5L 3s"
	
	local entries = {
		{"Scale structure"},
		{"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¢)"},

		{"TAMNAMS information"},
		{"Name", "diatonic"},
		{"Prefix", "dia-"},
		{"Abbrev.", "dia."},

		{"Related scales"},
		{"Parent MOS", "2L 3s"},
		{"Sister MOS", "2L 5s"},
		{"Daughter MOSes", "7L 5s, 5L 7s"},

		{"Equal tunings"},
		{"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¢)"},
	}
	
	return infobox.build_multilink("5L 2s (2/1-equivalent)", entries)
	
end

return p