Module:ET: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
CompactStar (talk | contribs)
No edit summary
These all looked too dumb tbh
 
(28 intermediate revisions by 5 users not shown)
Line 1: Line 1:
local rat = require('Module:Rational')
local rat = require("Module:Rational")
local seq = require('Module:Sequence')
local seq = require("Module:Sequence")
local p = {}
local p = {}


local common_suffix = {
local common_suffix = {
['3/2'] = 'f',
["3/2"] = "f",
['987/610'] = 'ϕ',
["2"] = "o",
['2'] = 'o',
["2/1"] = "o",
['2/1'] = 'o',
["3"] = "t",
['3'] = 't',
["3/1"] = "t",
['3/1'] = 't',
['1264/465'] = 'n',
['355/113'] = 'π'
}
}
local common_ratio = {
local common_ratio = {
['f'] = rat.new(3, 2),
["f"] = rat.new(3, 2),
['ϕ'] = rat.new(987, 610),
["o"] = 2,
['o'] = 2,
["t"] = 3,
['t'] = 3,
["ϕ"] = (1 + math.sqrt(5)) / 2,
['n'] = rat.new(1264, 465),
["n"] = math.exp(1),
['π'] = rat.new(355, 113)
["π"] = math.pi
}
}


Line 31: Line 28:
local equave_ratio = rat.as_ratio(equave)
local equave_ratio = rat.as_ratio(equave)
equave_ratio = equave_ratio:lower()
equave_ratio = equave_ratio:lower()
suffix = 'ed'
suffix = "ed"
if common_suffix[equave_ratio] then
if common_suffix[equave_ratio] then
suffix = suffix .. common_suffix[equave_ratio]
suffix = suffix .. common_suffix[equave_ratio]
Line 45: Line 42:
-- parse a ET structure
-- parse a ET structure
function p.parse(unparsed)
function p.parse(unparsed)
local size, suffix, equave = unparsed:match('^(%d+)([Ee][Dd](.+))$')
local size, suffix, equave = unparsed:match("^(%d+%.*%d*)([Ee][Dd](.+))$")
-- local size, suffix, equave = unparsed:match("^(%d+%.*%d*)([Cc]?[Ee][Dd]?[Tt]?(.*))$")
if equave == nil then
if equave == nil then
return nil
return nil
Line 80: Line 78:
function p.backslash_modifier(et)
function p.backslash_modifier(et)
if not rat.eq(et.equave, 2) then
if not rat.eq(et.equave, 2) then
return '\\' .. et.size .. et.suffix
return "\\" .. et.size .. et.suffix
end
end
return '\\' .. et.size
return "\\" .. et.size
end
end


Line 92: Line 90:
steps = steps or 1
steps = steps or 1
return 1200 * steps / et.size * math.log(rat.as_float(et.equave)) / math.log(2)
return 1200 * steps / et.size * math.log(rat.as_float(et.equave)) / math.log(2)
end
-- convert steps to hekts
function p.hekts(et, steps)
if et.size == 0 then
return 0
end
steps = steps or 1
return 1300 * steps / et.size * math.log(rat.as_float(et.equave)) / math.log(3)
end
end


Line 116: Line 123:
local t = 0
local t = 0
for factor, power in pairs(ratio) do
for factor, power in pairs(ratio) do
if type(factor) == 'number' then
if type(factor) == "number" then
t = t + power * p.approximate(et, factor)
t = t + power * p.approximate(et, factor)
end
end
Line 132: Line 139:
function p.is_zeta(et)
function p.is_zeta(et)
return seq.contains(seq.zeta_peak, et.size)
return seq.contains(seq.zeta_peak, et.size)
or seq.contains(seq.zeta_peak_integer, et.size)
or seq.contains(seq.zeta_integral, et.size)
or seq.contains(seq.zeta_integral, et.size)
or seq.contains(seq.zeta_gap, et.size)
or seq.contains(seq.zeta_gap, et.size)
Line 139: Line 147:
function p.why_zeta(et)
function p.why_zeta(et)
local zeta_peak = seq.contains(seq.zeta_peak, et.size)
local zeta_peak = seq.contains(seq.zeta_peak, et.size)
local zeta_peak_integer = seq.contains(seq.zeta_peak_integer, et.size)
local zeta_integral = seq.contains(seq.zeta_integral, et.size)
local zeta_integral = seq.contains(seq.zeta_integral, et.size)
local zeta_gap = seq.contains(seq.zeta_gap, et.size)
local zeta_gap = seq.contains(seq.zeta_gap, et.size)
local z = "The Riemann zeta function and tuning#Zeta EDO lists"
 
local markers = {}
local markers = {}
if zeta_peak then
if zeta_peak then
table.insert(markers, '[[The Riemann zeta function and tuning#Peak EDOs|zeta peak]]')
table.insert(markers, string.format("[[%s|Zeta peak]]", z))
elseif zeta_peak == nil then
elseif zeta_peak == nil then
table.insert(markers, '[[The Riemann zeta function and tuning#Peak EDOs|zeta peak?]]')
table.insert(markers, string.format("[[%s|Zeta peak?]]", z))
end
 
if zeta_peak_integer then
table.insert(markers, string.format("[[%s|Zeta peak integer]]", z))
elseif zeta_peak_integer == nil then
table.insert(markers, string.format("[[%s|Zeta peak integer?]]", z))
end
end
if zeta_integral then
if zeta_integral then
table.insert(markers, '[[The Riemann zeta function and tuning#Integral of Zeta EDOs|zeta integral]]')
table.insert(markers, string.format("[[%s|Zeta integral]]", z))
elseif zeta_integral == nil then
elseif zeta_integral == nil then
table.insert(markers, '[[The Riemann zeta function and tuning#Integral of Zeta EDOs|zeta integral?]]')
table.insert(markers, string.format("[[%s|Zeta integral?]]", z))
end
end
if zeta_gap then
if zeta_gap then
table.insert(markers, '[[The Riemann zeta function and tuning#Zeta Gap EDOs|zeta gap]]')
table.insert(markers, string.format("[[%s|Zeta gap]]", z))
elseif zeta_gap == nil then
elseif zeta_gap == nil then
table.insert(markers, '[[The Riemann zeta function and tuning#Zeta Gap EDOs|zeta gap?]]')
table.insert(markers, string.format("[[%s|Zeta gap?]]", z))
end
end
return table.concat(markers, '<br>')
 
return table.concat(markers, "<br />")
end
end


return p
return p

Latest revision as of 13:21, 19 July 2025

Module documentation[view] [edit] [history] [purge]

This module provides helper functions for equal-step tunings.

Functions

new
Returns an array consisting of the components of an equal-step tuning.
parse
Designed to convert strings in the format [number of steps]ed[equave] into an ET structure, and returns it via the new function. For example, ET.parse("12edo") returns an array containing {12, 2, "edo"}
as_string
Returns the string representation for an ET structure.
backslash_ratio
Converts steps to a proper ratio as a floating-point number.
backslash_display
Displays an ET structure in backslash form ([steps]\[number of divisions]).
cents
Converts the interval an ET structure represents to cents.
hekts
Converts the interval an ET structure represents to hekts (relative cent of 13edt).
approximate
Returns the floor, round, or ceiling of a particular ratio.
tempers_out
Determines if an ET tempers out a provided rational number.
is_highly_composite
Determines if an ET is highly composite.
is_zeta
Determines if an ET holds any zeta records.
why_zeta
Describes what specific properties an ET has if it is a zeta record ET.

local rat = require("Module:Rational")
local seq = require("Module:Sequence")
local p = {}

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,
	["ϕ"] = (1 + math.sqrt(5)) / 2,
	["n"] = math.exp(1),
	["π"] = math.pi
}

-- create a ET structure <size>ed<equave>
function p.new(size, equave, suffix)
	size = size or 12
	equave = equave or 2
	if suffix == nil then
		local equave_n, equave_m = rat.as_pair(equave)
		local equave_ratio = rat.as_ratio(equave)
		equave_ratio = equave_ratio:lower()
		suffix = "ed"
		if common_suffix[equave_ratio] then
			suffix = suffix .. common_suffix[equave_ratio]
		elseif equave_m == 1 then
			suffix = suffix .. equave_n
		else
			suffix = suffix .. equave_ratio
		end
	end
	return { size = size, equave = equave, suffix = suffix }
end

-- parse a ET structure
function p.parse(unparsed)
	local size, suffix, equave = unparsed:match("^(%d+%.*%d*)([Ee][Dd](.+))$")
	-- local size, suffix, equave = unparsed:match("^(%d+%.*%d*)([Cc]?[Ee][Dd]?[Tt]?(.*))$")
	if equave == nil then
		return nil
	end
	suffix = suffix:lower()
	size = tonumber(size)
	equave = common_ratio[equave] or rat.parse(equave)
	if size == nil or equave == nil then
		return nil
	end
	return p.new(size, equave, suffix)
end

-- construct a string representation for a ET structure
function p.as_string(et)
	return et.size .. et.suffix
end

-- convert steps to a proper ratio (except that it is a float approximation)
function p.backslash_ratio(et, steps)
	if et.size == 0 then
		return 1
	end
	return rat.as_float(et.equave) ^ (steps / et.size)
end

function p.backslash_display(et, steps)
	if et.size == 0 then
		return 1
	end
	return steps .. p.backslash_modifier(et)
end

function p.backslash_modifier(et)
	if not rat.eq(et.equave, 2) then
		return "\\" .. et.size .. et.suffix
	end
	return "\\" .. et.size
end

-- convert steps to cents
function p.cents(et, steps)
	if et.size == 0 then
		return 0
	end
	steps = steps or 1
	return 1200 * steps / et.size * math.log(rat.as_float(et.equave)) / math.log(2)
end

-- convert steps to hekts
function p.hekts(et, steps)
	if et.size == 0 then
		return 0
	end
	steps = steps or 1
	return 1300 * steps / et.size * math.log(rat.as_float(et.equave)) / math.log(3)
end

-- convert ratio to steps
-- ratio is a float!
-- towards is one of: -1 (floor), 0 (nearest), 1 (ceil)
function p.approximate(et, ratio, towards)
	towards = towards or 0
	if et.size == 0 then
		return 0
	end
	local exact = math.log(ratio) / math.log(rat.as_float(et.equave)) * et.size
	if towards < 0 then
		return math.floor(exact)
	elseif towards > 0 then
		return math.ceil(exact)
	else
		return math.floor(exact + 0.5)
	end
end

-- whether this ET tempers out the provided rational number
function p.tempers_out(et, ratio)
	local t = 0
	for factor, power in pairs(ratio) do
		if type(factor) == "number" then
			t = t + power * p.approximate(et, factor)
		end
	end
	return t == 0
end

-- determine whether ET is highly composite
function p.is_highly_composite(et)
	et.highly_composite = et.highly_composite or rat.is_highly_composite(et.size)
	return et.highly_composite
end

-- determine whether ET's size could be within one of zeta function-related sequences
function p.is_zeta(et)
	return seq.contains(seq.zeta_peak, et.size)
		or seq.contains(seq.zeta_peak_integer, et.size)
		or seq.contains(seq.zeta_integral, et.size)
		or seq.contains(seq.zeta_gap, et.size)
end

-- describe why
function p.why_zeta(et)
	local zeta_peak = seq.contains(seq.zeta_peak, et.size)
	local zeta_peak_integer = seq.contains(seq.zeta_peak_integer, et.size)
	local zeta_integral = seq.contains(seq.zeta_integral, et.size)
	local zeta_gap = seq.contains(seq.zeta_gap, et.size)
	local z = "The Riemann zeta function and tuning#Zeta EDO lists"

	local markers = {}
	if zeta_peak then
		table.insert(markers, string.format("[[%s|Zeta peak]]", z))
	elseif zeta_peak == nil then
		table.insert(markers, string.format("[[%s|Zeta peak?]]", z))
	end

	if zeta_peak_integer then
		table.insert(markers, string.format("[[%s|Zeta peak integer]]", z))
	elseif zeta_peak_integer == nil then
		table.insert(markers, string.format("[[%s|Zeta peak integer?]]", z))
	end

	if zeta_integral then
		table.insert(markers, string.format("[[%s|Zeta integral]]", z))
	elseif zeta_integral == nil then
		table.insert(markers, string.format("[[%s|Zeta integral?]]", z))
	end

	if zeta_gap then
		table.insert(markers, string.format("[[%s|Zeta gap]]", z))
	elseif zeta_gap == nil then
		table.insert(markers, string.format("[[%s|Zeta gap?]]", z))
	end

	return table.concat(markers, "<br />")
end

return p