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
}

-- 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 = size .. '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

-- 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

-- determine whether the temperament is highly composite
function p.is_highly_composite(et)
	et.highly_composite = et.highly_composite or rat.is_highly_composite(et.size)
	et.superabundant = et.superabundant or rat.is_superabundant(et.size)
	return et.highly_composite or et.superabundant
end
p.is_highly_melodic = p.is_highly_composite

-- describe why
function p.why_highly_melodic(et, debug_mode)
	et.highly_composite = et.highly_composite or rat.is_highly_composite(et.size)
	et.superabundant = et.superabundant or rat.is_superabundant(et.size)
	if et.highly_composite and et.superabundant then
		if debug_mode then
			return 'highly composite, superabundant'
		else
			return 'highly composite,<br>superabundant'
		end
	elseif et.highly_composite then
		return 'highly composite'
	elseif et.superabundant then
		return 'superabundant'
	else
		return 'no'
	end
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) ~= false
		or seq.contains(seq.integral_zeta, et.size) ~= false
		or seq.contains(seq.zeta_gap, et.size) ~= false
end

-- describe why
function p.why_zeta(et, debug_mode)
	local zeta_peak = seq.contains(seq.zeta_peak, et.size)
	local integral_zeta = seq.contains(seq.integral_zeta, et.size)
	local zeta_gap = seq.contains(seq.zeta_gap, et.size)
	
	local markers = {}
	if zeta_peak then
		if debug_mode then
			table.insert(markers, '[[The Riemann zeta function and tuning#Peak EDOs|zeta peak]]')
		else
			table.insert(markers, 'peak')
		end
	elseif zeta_peak == nil then
		if debug_mode then
			table.insert(markers, '[[The Riemann zeta function and tuning#Peak EDOs|zeta peak?]]')
		else
			table.insert(markers, 'peak?')
		end
	end
	if integral_zeta then
		if debug_mode then
			table.insert(markers, '[[The Riemann zeta function and tuning#Integral of Zeta EDOs|integral of zeta]]')
		else
			table.insert(markers, 'integral')
		end
	elseif integral_zeta == nil then
		if debug_mode then
			table.insert(markers, '[[The Riemann zeta function and tuning#Integral of Zeta EDOs|integral of zeta?]]')
		else
			table.insert(markers, 'integral?')
		end
	end
	if zeta_gap then
		if debug_mode then
			table.insert(markers, '[[The Riemann zeta function and tuning#Zeta Gap EDOs|zeta gap]]')
		else
			table.insert(markers, 'gap')
		end
	elseif zeta_gap == nil then
		if debug_mode then
			table.insert(markers, '[[The Riemann zeta function and tuning#Zeta Gap EDOs|zeta gap?]]')
		else
			table.insert(markers, 'gap?')
		end
	end
	return table.concat(markers, ', ')
end

return p