Module:ET: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
Inthar (talk | contribs)
No edit summary
Inthar (talk | contribs)
No edit summary
Line 66: Line 66:
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.suffix
return '\\' .. et.size .. et.suffix
end
end
return ''
return '\\' .. et.size
end
end



Revision as of 20:18, 31 March 2023

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

This module provides helper functions for equal-step tunings.

Introspection summary for Module:ET 
Functions provided (11)
Line Function Params Description
19 new (size, equave, suffix) Returns an array consisting of the components of an equal-step tuning.
39 parse (unparsed) Converts an equal tuning as a string into a Lua table.
54 as_string (et) Returns the ET as a string.
59 backslash_ratio (et, steps) Converts steps to a proper ratio as a floating-point number.
66 backslash_modifier (et)
74 cents (et, steps) Converts an interval of the ET into cents,  ¢.
85 approximate (et, ratio, towards) Returns the floor, round, or ceiling of a particular ratio.
101 tempers_out (et, ratio) Determines whether an ET tempers out a provided rational number.
112 is_highly_composite (et) Determines whether an ET is highly composite.
118 is_zeta (et) Determines if an ET holds any zeta records.
125 why_zeta (et) Describes what specific properties an ET has if it is a zeta record ET.
Lua modules required (2)
Variable Module Functions used
rat Module:Rational new
as_pair
as_ratio
parse
as_float
eq
is_highly_composite
seq Module:Sequence contains

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
}

-- 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+)([Ee][Dd](.+))$')
	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_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 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_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_integral = seq.contains(seq.zeta_integral, et.size)
	local zeta_gap = seq.contains(seq.zeta_gap, et.size)
	
	local markers = {}
	if zeta_peak then
		table.insert(markers, '[[The Riemann zeta function and tuning#Peak EDOs|zeta peak]]')
	elseif zeta_peak == nil then
		table.insert(markers, '[[The Riemann zeta function and tuning#Peak EDOs|zeta peak?]]')
	end
	if zeta_integral then
		table.insert(markers, '[[The Riemann zeta function and tuning#Integral of Zeta EDOs|zeta integral]]')
	elseif zeta_integral == nil then
		table.insert(markers, '[[The Riemann zeta function and tuning#Integral of Zeta EDOs|zeta integral?]]')
	end
	if zeta_gap then
		table.insert(markers, '[[The Riemann zeta function and tuning#Zeta Gap EDOs|zeta gap]]')
	elseif zeta_gap == nil then
		table.insert(markers, '[[The Riemann zeta function and tuning#Zeta Gap EDOs|zeta gap?]]')
	end
	return table.concat(markers, '<br>')
end

return p