Module:Rank-n scale

From Xenharmonic Wiki
Jump to navigation Jump to search

Documentation transcluded from /doc
Note: Do not invoke this module directly; use the corresponding template instead: Template:Rank-n scale.
Icon-Todo.png Todo: Documentation

local infobox = require('Module:Infobox')

local p = {}

local function rotate_word(str, n) -- rotate word s by n steps
    return string.sub(str, n+1, #str) .. string.sub(str, 1, n)
end    

local function rotate_word_full(str)
	local full = {}
	for i = 1, #str do
		full[i] = rotate_word(str, i-1)
	end
	return full
end

local function bright_dark_chiral(str)
	local full = rotate_word_full(str)
	local bright, dark = str, str
	for i = 2, #str do
		if bright > full[i] then
			bright = full[i]
		end
		if dark < full[i] then 
			dark = full[i]
		end
	end
	
	local bright2, dark2 = string.reverse(str), string.reverse(str)
	for i = 2, #str do
		local rev = string.reverse(full[i])
		if bright2 > rev then
			bright2 = rev
		end
		if dark2 < rev then 
			dark2 = rev
		end
	end
	if bright2 == bright then
		return {["blh"] = bright, ["brh"] = bright, ["dlh"] = dark, ["drh"] = dark, ["chiral"] = "no"}
	elseif bright2 < bright then
		return {["blh"] = bright, ["brh"] = bright2, ["dlh"] = dark, ["drh"] = dark2, ["chiral"] = "yes"}
	else
		return {["blh"] = bright2, ["brh"] = bright, ["dlh"] = dark2, ["drh"] = dark, ["chiral"] = "yes"}
	end
end

local function table_length(T)
	local count = 0
	for _ in pairs(T) do count = count + 1 end
	return count
end

local function abelianize(str) -- sorts string by letter. from rosetta code because I'm lazy.
	local t = {} str:gsub("(%S)", function(c) t[#t+1]=c end) -- use "(.)" as pattern to preserve whitespace
    table.sort(t, function(a,b) return a<b end) -- implicitly
    return table.concat(t)
end

local function list_step_varieties(str)
	local strstr = str .. str
	local step_vars = {}
	for i = 1, math.floor(#str/2) do
		local r = {}
		for j = 1, #str do
			local u = abelianize(string.sub(strstr, j, j+i-1))
			r[u] = true
		end
		step_vars[i] = r
	end
	return step_vars
end

local function max_variety(str)
	local lsv = list_step_varieties(str)
	local max_var = 0
	local is_strict = "yes"
	for i = 1, math.floor(#str/2) do
		i_step_var = table_length(lsv[i])
		if i_step_var > max_var then
			if max_var ~= 0 then
				is_strict = "no"
			end
			max_var = i_step_var
		end
	end
	return {["max variety"] = max_var, ["is strict?"] = is_strict}
end

function p.create_infobox(frame)
	local scale = frame.args["scale"]
	local mv = max_variety(scale)
	local bdc = bright_dark_chiral(scale)
	
	local brightest_mode
	local darkest_mode
	
	if bdc["chiral"] == "yes" then
		brightest_mode = {"Brightest mode", "LH " .. bdc["blh"] .. "\nRH ".. bdc["brh"]}
		darkest_mode = {"Darkest mode", "LH " .. bdc["dlh"] .. "\nRH ".. bdc["drh"]}
	else
		brightest_mode = {"Brightest mode", bdc["brh"]}
		darkest_mode = {"Darkest mode", bdc["drh"]}
	end
	
	local entries = {
		{"Scale structure"},
		
		{"Step pattern"},
		brightest_mode,
		darkest_mode,
		
		{"Is chiral?", bdc["chiral"]},
		
		{"Maximum variety", mv["max variety"]},
		{"Has strict variety?", mv["is strict?"]},
	}
	return infobox.build_multilink(scale, entries)
end

-- {"Mean variety", " "}
-- {"Is generator-offset?", " "},
-- {"Tuning ranges"},
-- {"Parent scales"},
-- {"Descendant scales"},

return p