Module:TAMNAMS: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
Added code skeleton for function to decode an interval to MmAPd
Ganaram inukshuk (talk | contribs)
Added base functionality for quality-decode function (large/small -> MmAPd)
Line 6: Line 6:
local utils = require('Module:Utils')
local utils = require('Module:Utils')
local p = {}
local p = {}
-- TODO
-- - Adjust formatting of abbreviations for decode function


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 208: Line 211:
function p.preprocess_step_ratio(step_ratio)
function p.preprocess_step_ratio(step_ratio)
return (#step_ratio == 2 and type(step_ratio[1]) == 'number' and type(step_ratio[2]) == 'number') and rat.new(step_ratio[1], step_ratio[2]) or step_ratio
return (#step_ratio == 2 and type(step_ratio[1]) == 'number' and type(step_ratio[2]) == 'number') and rat.new(step_ratio[1], step_ratio[2]) or step_ratio
end
function p.preprocess_scalesig(input_mos)
end
end


Line 217: Line 223:
-- Can accept either a mos (defined by mos module) or its scalesig.
-- Can accept either a mos (defined by mos module) or its scalesig.
function p.lookup_name(input_mos)  
function p.lookup_name(input_mos)  
local scalesig = type(input_mos) == "string" and input_mos or mos.as_string(input_mos)
local scalesig
if type(input_mos) == "string" then
scalesig = input_mos
elseif type(input_mos) == "table" then
scalesig = mos.as_string(input_mos)
end
return p.tamnams_name[scalesig]
return p.tamnams_name[scalesig]
end
end
Line 224: Line 235:
-- Can accept either a mos (defined by mos module) or its scalesig.
-- Can accept either a mos (defined by mos module) or its scalesig.
function p.lookup_prefix(input_mos)  
function p.lookup_prefix(input_mos)  
local scalesig = type(input_mos) == "string" and input_mos or mos.as_string(input_mos)
local scalesig
if type(input_mos) == "string" then
scalesig = input_mos
elseif type(input_mos) == "table" then
scalesig = mos.as_string(input_mos)
end
return p.tamnams_prefix[scalesig]
return p.tamnams_prefix[scalesig]
end
end
Line 231: Line 247:
-- Can accept either a mos (defined by mos module) or its scalesig.
-- Can accept either a mos (defined by mos module) or its scalesig.
function p.lookup_abbrev(input_mos)  
function p.lookup_abbrev(input_mos)  
local scalesig = type(input_mos) == "string" and input_mos or mos.as_string(input_mos)
local scalesig
if type(input_mos) == "string" then
scalesig = input_mos
elseif type(input_mos) == "table" then
scalesig = mos.as_string(input_mos)
end
return p.tamnams_abbrev[scalesig]
return p.tamnams_abbrev[scalesig]
end
end
Line 280: Line 301:
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


function p.decode_interval_quality(interval, inupt_mos, is_abbrev, mos_prefix)
function p.decode_interval_quality(interval, input_mos, abbrev_format, mos_prefix)
local mos_prefix = p.lookup_prefix(input_mos) or mos_prefix
local abbrev_format = abbrev_format or none
local mos_prefix = p.lookup_prefix(input_mos) or mos_prefix or "mos"
local interval = mos.period_reduce(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)
local step_count = mos.interval_step_count(interval)
local base_interval = mos.interval_from_mos(input_mos, step_count)
local is_period_interval = interval == mos.period_step_count(input_mos)
-- Determine what "special" type the interval is so that the designations
local is_bright_gen = interval == mos.bright_gen_step_count(input_mos)
-- of augmented/perfect/diminished (APd) apply, skipping major/minor (Mm).
local is_dark_gen = interval == mos.dark_gen_step_count(input_mos)
-- 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)
-- If the interval is a period or generator of a non-root mos, then it's
-- perfectable.
local is_perfectable = (is_period or is_bright_gen or is_dark_gen) and not is_root_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
local is_root_mos = input_mos.nL == input_mos.ns
-- Get chroma count and adjust as needed
local chroma_count = 0
local quality = ""
if is_perfectable then
if is_equave or 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)
end
quality = p.chroma_count_to_perfectable_interval_quality(chroma_count, abbrev_format)
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)
quality = p.chroma_count_to_nonperfectable_interval_quality(chroma_count, abbrev_format)
end
return string.format("%s %d-%s%s", quality, step_count, mos_prefix, abbrev_format == "none" and "step" or "s")
end
end


-- Given the chroma count for a perfectable interval, return the quality it
-- corresponds to:
--  4 = 4x augmented
--  3 = 3x augmented
--  2 = 2x augmented
--  1 = augmented
--  0 = perfect
-- -1 = diminished
-- -2 = 2x diminished
-- -3 = 3x diminished
-- -4 = 4x diminished
function p.chroma_count_to_perfectable_interval_quality(chromas, abbrev_format)
local abbrev_format = abbrev_format or "none"
-- Get absolute value of chroma count
local chroma_abs = math.abs(chromas)
-- Get quality
local quality = ""
if abbrev_format == "none" or abbrev_format == "NONE" then
if chromas < 0 then
quality = "diminished"
elseif chromas > 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 == "short" or abbrev_format == "SHORT" then
if chromas < 0 then
quality = "Dim."
elseif chromas > 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 chromas < 0 then
quality = "d"
elseif chromas > 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
return quality
end
-- Given the chroma count for a nonperfectable interval, return the quality it
-- corresponds to:
--  4 = 4x augmented
--  3 = 3x augmented
--  2 = 2x augmented
--  1 = augmented
--  0 = major
-- -1 = minor
-- -2 = diminished
-- -3 = 2x diminished
-- -4 = 3x diminished
-- -5 = 4x diminished
function p.chroma_count_to_nonperfectable_interval_quality(chromas, abbrev_format)
local abbrev_format = abbrev_format or "none"
-- Is the interval major
local is_positive = chromas >= 0
-- Get absolute value of chroma count
local chroma_abs = math.abs(chromas)
if not is_positive then
chroma_abs = chroma_abs - 1
end
-- Get quality
local quality = ""
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 == "short" or abbrev_format == "SHORT" 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
return quality
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
Line 301: Line 494:
function p.tester()
function p.tester()
--return p.lookup_step_ratio_range(rat.new(10, 1), rat.new(1, 0), true)
--return p.lookup_step_ratio_range(rat.new(10, 1), rat.new(1, 0), true)
return p.lookup_step_ratio_range(rat.new(6,5), rat.new(1,1), true)
--return p.lookup_step_ratio_range(rat.new(6,5), rat.new(1,1), true)
local abbrev_code = "short"
local input_mos = mos.new(5,2)
--[[
return
p.chroma_count_to_nonperfectable_interval_quality( 4, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality( 3, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality( 2, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality( 1, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality( 0, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality(-1, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality(-2, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality(-3, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality(-4, abbrev_code) .. "\n" ..
p.chroma_count_to_nonperfectable_interval_quality(-5, abbrev_code)
]]--
local output_string = 
p.decode_interval_quality({['L']= 6,['s']=-4}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']= 5,['s']=-3}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']= 4,['s']=-2}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']= 3,['s']=-1}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']= 2,['s']= 0}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']= 1,['s']= 1}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']= 0,['s']= 2}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']=-1,['s']= 3}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']=-2,['s']= 4}, input_mos, abbrev_code) .. "\n" ..
p.decode_interval_quality({['L']=-3,['s']= 5}, input_mos, abbrev_code)
return output_string
end
end


return p
return p

Revision as of 05:32, 4 June 2024

Module documentation[view] [edit] [history] [purge]
This module primarily serves as a library for other modules and has no corresponding template.

This module is designed to handle TAMNAMS as it pertains to MOS scales. It is meant to be used with other modules, rather than something invoked directly or as part of a template.

Introspection summary for Module:TAMNAMS 
Functions provided (12)
Line Function Params
211 preprocess_step_ratio (step_ratio)
215 preprocess_scalesig (input_mos)
224 lookup_name (input_mos)
236 lookup_prefix (input_mos)
248 lookup_abbrev (input_mos)
260 lookup_step_ratio (step_ratio, use_extended)
274 lookup_step_ratio_range (step_ratio_1, step_ratio_2, use_extended)
296 lookup_named_ancestor (input_mos)
303 decode_interval_quality (interval, input_mos, abbrev_format, mos_prefix)
370 chroma_count_to_perfectable_interval_quality (chromas, abbrev_format)
433 chroma_count_to_nonperfectable_interval_quality (chromas, abbrev_format)
494 tester none
Lua modules required (3)
Variable Module Functions used
mos Module:MOS as_string
normalize_interval
interval_step_count
period_step_count
bright_gen_step_count
dark_gen_step_count
interval_chroma_count
new
rat Module:Rational new
as_ratio
as_float
utils Module:Utils dependency not used

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


-- 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 = {}

-- TODO
-- - Adjust formatting of abbreviations for decode function

--------------------------------------------------------------------------------
------------------------------- LOOKUP TABLES ----------------------------------
--------------------------------------------------------------------------------

-- Lookup table for tamnams step ratios
p.tamnams_ratios = {
	['1:1'] = 'equalized',
	['4:3'] = 'supersoft',
	['3:2'] = 'soft',
	['5:3'] = 'semisoft',
	['2:1'] = 'basic',
	['5:2'] = 'semihard',
	['3:1'] = 'hard',
	['4:1'] = 'superhard',
	['1:0'] = 'collapsed'
}

-- 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 official tamnams names
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.
function p.preprocess_step_ratio(step_ratio)
	return (#step_ratio == 2 and type(step_ratio[1]) == 'number' and type(step_ratio[2]) == 'number') and rat.new(step_ratio[1], step_ratio[2]) or step_ratio
end

function p.preprocess_scalesig(input_mos)
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
	if type(input_mos) == "string" then
		scalesig = input_mos
	elseif type(input_mos) == "table" then
		scalesig = mos.as_string(input_mos)
	end
	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
	if type(input_mos) == "string" then
		scalesig = input_mos
	elseif type(input_mos) == "table" then
		scalesig = mos.as_string(input_mos)
	end
	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
	if type(input_mos) == "string" then
		scalesig = input_mos
	elseif type(input_mos) == "table" then
		scalesig = mos.as_string(input_mos)
	end
	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

function p.lookup_named_ancestor(input_mos)
end

--------------------------------------------------------------------------------
-------------------------- ENCODE/DECODE FUNCTIONS -----------------------------
--------------------------------------------------------------------------------

function p.decode_interval_quality(interval, input_mos, abbrev_format, mos_prefix)
	local abbrev_format = abbrev_format or none
	local mos_prefix = p.lookup_prefix(input_mos) or mos_prefix or "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)
	
	-- If the interval is a period or generator of a non-root mos, then it's
	-- perfectable.
	local is_perfectable = (is_period or is_bright_gen or is_dark_gen) and not is_root_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
	
	-- Get chroma count and adjust as needed
	local chroma_count = 0
	local quality = ""
	if is_perfectable then
		if is_equave or 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)
		end
		quality = p.chroma_count_to_perfectable_interval_quality(chroma_count, abbrev_format)
	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)
		quality = p.chroma_count_to_nonperfectable_interval_quality(chroma_count, abbrev_format)
	end
	
	return string.format("%s %d-%s%s", quality, step_count, mos_prefix, abbrev_format == "none" and "step" or "s")
end

-- Given the chroma count for a perfectable interval, return the quality it
-- corresponds to:
--  4 = 4x augmented
--  3 = 3x augmented
--  2 = 2x augmented
--  1 = augmented
--  0 = perfect
-- -1 = diminished
-- -2 = 2x diminished
-- -3 = 3x diminished
-- -4 = 4x diminished
function p.chroma_count_to_perfectable_interval_quality(chromas, abbrev_format)
	local abbrev_format = abbrev_format or "none"
	
	-- Get absolute value of chroma count
	local chroma_abs = math.abs(chromas)
	
	-- Get quality
	local quality = ""
	if abbrev_format == "none" or abbrev_format == "NONE" then
		if chromas < 0 then
			quality = "diminished"
		elseif chromas > 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 == "short" or abbrev_format == "SHORT" then
		if chromas < 0 then
			quality = "Dim."
		elseif chromas > 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 chromas < 0 then
			quality = "d"
		elseif chromas > 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
	
	return quality
end

-- Given the chroma count for a nonperfectable interval, return the quality it
-- corresponds to:
--  4 = 4x augmented
--  3 = 3x augmented
--  2 = 2x augmented
--  1 = augmented
--  0 = major
-- -1 = minor
-- -2 = diminished
-- -3 = 2x diminished
-- -4 = 3x diminished
-- -5 = 4x diminished
function p.chroma_count_to_nonperfectable_interval_quality(chromas, abbrev_format)
	local abbrev_format = abbrev_format or "none"
	
	-- Is the interval major
	local is_positive = chromas >= 0
	
	-- Get absolute value of chroma count
	local chroma_abs = math.abs(chromas)
	if not is_positive then
		chroma_abs = chroma_abs - 1
	end
	
	-- Get quality
	local quality = ""
	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 == "short" or abbrev_format == "SHORT" 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
	
	return quality
end

--------------------------------------------------------------------------------
----------------------------- TESTER FUNCTION ----------------------------------
--------------------------------------------------------------------------------

function p.tester()
	--return p.lookup_step_ratio_range(rat.new(10, 1), rat.new(1, 0), true)
	--return p.lookup_step_ratio_range(rat.new(6,5), rat.new(1,1), true)
	local abbrev_code = "short"
	local input_mos = mos.new(5,2)
	--[[
	return
		p.chroma_count_to_nonperfectable_interval_quality( 4, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality( 3, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality( 2, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality( 1, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality( 0, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality(-1, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality(-2, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality(-3, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality(-4, abbrev_code) .. "\n" ..
		p.chroma_count_to_nonperfectable_interval_quality(-5, abbrev_code)
		]]--
	local output_string =  
		p.decode_interval_quality({['L']= 6,['s']=-4}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']= 5,['s']=-3}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']= 4,['s']=-2}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']= 3,['s']=-1}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']= 2,['s']= 0}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']= 1,['s']= 1}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']= 0,['s']= 2}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']=-1,['s']= 3}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']=-2,['s']= 4}, input_mos, abbrev_code) .. "\n" ..
		p.decode_interval_quality({['L']=-3,['s']= 5}, input_mos, abbrev_code)
	return output_string
end

return p