Module:ED intro

Revision as of 00:22, 12 April 2024 by Ganaram inukshuk (talk | contribs) (Rewrote code for better handling of ed types; added edo compare function (for outputting things like "12edf corresponds to 20.5edo"), but requires further rewriting/testing)
Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:ED intro.

This module automatically fills in an introduction for an equal-step tuning. It has presets for the most common equivalences (octave, tritave, and perfect fifth) and also supports arbitrary equivalences and arithmetic pitch sequences such as 88cET.

Introspection summary for Module:ED intro 
Functions provided (11)
Line Function Params
28 edo_intro (ed)
55 edt_intro (ed)
83 edf_intro (ed)
111 edh_intro (ed, harmonic)
142 edr_intro (ed, ratio)
172 edc_intro (ed, cents)
200 parse (unparsed)
252 edo_compare (ed)
290 _ed_intro (main) (ed)
313 ed_intro_tester none
330 ed_intro (invokable) (frame)
Lua modules required (3)
Variable Module Functions used
ord Module:Ordinal _ordinal
rat Module:Rational new
cents
as_ratio
utils Module:Utils _round

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


local ord = require('Module:Ordinal')
local utils = require('Module:Utils')
local rat = require('Module:Rational')
local p = {}

-- TODO: Add support for the following
-- - edc - equal divisions of a non-integer constant or cent value

-- Notes:
-- - Edo and edt are technically edh's, and edf an edr; these are their own
--   types because their intros have specific wording.
-- - Edh is a special case of edr, where the ratio is h/1.
-- - Under harmonotonic tuning, edc and edr are also called ASp and APSp
--   (ambitonal sequence and arithmetic pitch sequence, respectively). These
--   were formerly called equal-step tunings but were reclassified as 1ed.
-- - Equal divisions of irrational constants (such as pi and e) are not very
--   common, but count as equal divisions of arbitrary cent values.

local ED_TYPE_EDO = "EDO"
local ED_TYPE_EDT = "EDT"
local ED_TYPE_EDF = "EDF"
local ED_TYPE_EDH = "EDH"
local ED_TYPE_EDR = "EDR"
local ED_TYPE_EDC = "EDC"
local ED_TYPE_DEFAULT = "UNKNOWN_TYPE"

-- Separate function for edo intro
function p.edo_intro(ed)
	local ed = ed or 12
	
	-- Exactly or about? Round to 3 DPs
	local edstep_size = 1200 / ed
	local edstep_size_rounded = utils._round(edstep_size, 3)
	local is_exact = edstep_size - edstep_size_rounded == 0
	
	local ordinal = ord._ordinal(ed)
	
	local intro_text = ""
	if ed == 1 then
		intro_text = "'''1 equal division of the octave''' (abbreviated '''1edo''' or '''1ed2'''), also called '''1-tone equal temperament''' ('''1tet''') or '''1 equal temperament''' ('''1et''') when viewed under a [[regular temperament]] perspective, is the [[tuning system]] that uses [[equal]] steps of 2/1 (one [[octave]]), or exactly/about ''s'' [[¢]]."	
	else
		intro_text = "'''''k'' equal divisions of the octave''' (abbreviated '''''k''edo''' or '''''k''ed2'''), also called '''''k''-tone equal temperament''' ('''''k''tet''') or '''''k'' equal temperament''' ('''''k''et''') when viewed under a [[regular temperament]] perspective, is the [[tuning system]] that divides the [[octave]] into ''k'' [[equal]] parts of exactly/about ''s'' [[¢]] each. Each step represents a [[frequency ratio]] of 2<sup>1/''k''</sup>, or the ''kth'' root of 2."	
	end

	-- Replace certain strings with the intended final versions
	intro_text = string.gsub(intro_text, "''k''", ed)
	intro_text = string.gsub(intro_text, "exactly/about", (is_exact and "exactly" or "about"))
	intro_text = string.gsub(intro_text, "''s''", string.format("%.3f", edstep_size))
	intro_text = string.gsub(intro_text, "''kth''", ord._ordinal(ed))
	
	return intro_text
end

-- Separate function for edt intro
function p.edt_intro(ed)
	local ed = ed or 13
	
	-- Exactly or about? Round to 3 DPs
	local equave_in_cents = math.log(3) * 1200
	local edstep_size = equave_in_cents / math.log(2) / ed
	local edstep_size_rounded = utils._round(edstep_size, 3)
	local is_exact = edstep_size - edstep_size_rounded == 0
	
	local ordinal = ord._ordinal(ed)
	
	local intro_text = ""
	if ed == 1 then
		intro_text = "'''1 equal division of the tritave''', '''perfect twelfth''', or '''3rd harmonic''' (abbreviated '''1edt''' or '''1ed3'''), is a [[nonoctave]] [[tuning system]] that uses [[equal]] steps of [[3/1]] (one tritave), or exactly/about ''s'' [[¢]]."	
	else
		intro_text = "'''''k'' equal divisions of the tritave''', '''perfect twelfth''', or '''3rd harmonic''' (abbreviated '''''k''edt''' or '''''k''ed3'''), is a [[nonoctave]] [[tuning system]] that divides the interval of [[3/1]] into ''k'' [[equal]] parts of exactly/about ''s'' [[¢]] each. Each step represents a [[frequency ratio]] of 3<sup>1/''k''</sup>, or the ''kth'' root of 3."	
	end

	-- Replace certain strings with the intended final versions
	intro_text = string.gsub(intro_text, "''k''", ed)
	intro_text = string.gsub(intro_text, "exactly/about", (is_exact and "exactly" or "about"))
	intro_text = string.gsub(intro_text, "''s''", string.format("%.3f", edstep_size))
	intro_text = string.gsub(intro_text, "''kth''", ord._ordinal(ed))
	
	return intro_text
end

-- Separate function for edf intro
function p.edf_intro(ed)
	local ed = ed or 7
	
	-- Exactly or about? Round to 3 DPs
	local equave_in_cents = math.log(3/2) * 1200
	local edstep_size = equave_in_cents / math.log(2) / ed
	local edstep_size_rounded = utils._round(edstep_size, 3)
	local is_exact = edstep_size - edstep_size_rounded == 0
	
	local ordinal = ord._ordinal(ed)
	
	local intro_text = ""
	if ed == 1 then
		intro_text = "'''1 equal division of the perfect fifth''' (abbreviated '''1edf''' or '''1ed3/2''') is a [[nonoctave]] [[tuning system]] that uses [[equal]] steps of [[3/2]] (one perfect fifth), or exactly/about ''s'' [[¢]]."	
	else
		intro_text = "'''''k'' equal divisions of the perfect fifth''' (abbreviated '''''k''edf''' or '''''k''ed3/2''') is a [[nonoctave]] [[tuning system]] that divides the interval of [[3/2]] into ''k'' [[equal]] parts of exactly/about ''s'' [[¢]] each. Each step represents a [[frequency ratio]] of (3/2)<sup>1/''k''</sup>, or the ''kth'' root of 3/2."	
	end

	-- Replace certain strings with the intended final versions
	intro_text = string.gsub(intro_text, "''k''", ed)
	intro_text = string.gsub(intro_text, "exactly/about", (is_exact and "exactly" or "about"))
	intro_text = string.gsub(intro_text, "''s''", string.format("%.3f", edstep_size))
	intro_text = string.gsub(intro_text, "''kth''", ord._ordinal(ed))
	
	return intro_text
end

-- Separate function for edh intro (arbitrary harmonic)
function p.edh_intro(ed, harmonic)
	local ed = ed or 12
	local harmonic = harmonic or 4
	
	-- Exactly or about? Round to 3 DPs
	local equave_in_cents = math.log(harmonic) * 1200 
	local edstep_size = equave_in_cents / math.log(2) / ed
	local edstep_size_rounded = utils._round(edstep_size, 3)
	local is_exact = edstep_size - edstep_size_rounded == 0
	
	local ordinal = ord._ordinal(ed)
	
	local intro_text = ""
	if ed == 1 then
		intro_text = "'''1 equal division of the hth harmonic''' (abbreviated '''1ed''h''''') is a [[nonoctave]] [[tuning system]] that uses [[equal]] steps of [[''h''/1]], or exactly/about ''s'' [[¢]]."	
	else
		intro_text = "'''''k'' equal divisions of the hth harmonic''' (abbreviated '''''k''ed''h''''') is a [[nonoctave]] [[tuning system]] that divides the interval of [[''h''/1]] into ''k'' [[equal]] parts of exactly/about ''s'' [[¢]] each. Each step represents a [[frequency ratio]] of ''h''<sup>1/''k''</sup>, or the ''kth'' root of ''h''."	
	end

	-- Replace certain strings with the intended final versions
	intro_text = string.gsub(intro_text, "''k''", ed)
	intro_text = string.gsub(intro_text, "exactly/about", (is_exact and "exactly" or "about"))
	intro_text = string.gsub(intro_text, "''s''", string.format("%.3f", edstep_size))
	intro_text = string.gsub(intro_text, "''kth''", ord._ordinal(ed))
	intro_text = string.gsub(intro_text, "hth", ord._ordinal(harmonic))
	intro_text = string.gsub(intro_text, "''h''", harmonic)
	
	return intro_text
end

-- Separate function for edr intro (arbitrary ratio)
function p.edr_intro(ed, ratio)
	local ed = ed or 12
	local ratio = ratio or rat.new(9,4)
	
	-- Exactly or about? Round to 3 DPs
	local equave_in_cents = rat.cents(ratio)
	local edstep_size = equave_in_cents / ed
	local edstep_size_rounded = utils._round(edstep_size, 3)
	local is_exact = edstep_size - edstep_size_rounded == 0
	
	local ordinal = ord._ordinal(ed)
	
	local intro_text = ""
	if ed == 1 then
		intro_text = "'''1 equal division of ''p/q''''' (abbreviated '''1ed''p/q''''') is a [[nonoctave]] [[tuning system]] that uses [[equal]] steps of [[''p/q'']], or exactly/about ''s'' [[¢]]."	
	else
		intro_text = "'''''k'' equal divisions of ''p/q''''' (abbreviated '''''k''ed''p/q''''') is a [[nonoctave]] [[tuning system]] that divides the interval of [[''p/q'']] into ''k'' [[equal]] parts of exactly/about ''s'' [[¢]] each. Each step represents a [[frequency ratio]] of (''p/q'')<sup>1/''k''</sup>, or the ''kth'' root of ''p/q''."	
	end

	-- Replace certain strings with the intended final versions
	intro_text = string.gsub(intro_text, "''k''", ed)
	intro_text = string.gsub(intro_text, "exactly/about", (is_exact and "exactly" or "about"))
	intro_text = string.gsub(intro_text, "''s''", string.format("%.3f", edstep_size))
	intro_text = string.gsub(intro_text, "''kth''", ord._ordinal(ed))
	intro_text = string.gsub(intro_text, "''p/q''", rat.as_ratio(ratio))
	
	return intro_text
end

-- Separate function for edc (arbitrary cent value)
function p.edc_intro(ed, cents)
	local ed = ed or 1
	local cents = cents or 97.5
	
	-- Exactly or about? Round to 3 DPs
	local edstep_size = cents / ed
	local edstep_size_rounded = utils._round(edstep_size, 3)
	local is_exact = edstep_size - edstep_size_rounded == 0
	
	local ordinal = ord._ordinal(ed)
	
	local intro_text = ""
	if ed == 1 then
		intro_text = "'''1 equal division of ''c''¢''' (abbreviated '''1ed''c''¢''') is a [[nonoctave]] [[tuning system]] that uses [[equal]] steps of ''c'' [[¢]]."	
	else
		intro_text = "'''''k'' equal divisions of ''c''¢''' (abbreviated '''''k''ed''c''¢''') is a [[nonoctave]] [[tuning system]] that divides the interval of ''c''¢ into ''k'' [[equal]] parts of exactly/about ''s'' [[¢]] each."	
	end

	-- Replace certain strings with the intended final versions
	intro_text = string.gsub(intro_text, "''k''", ed)
	intro_text = string.gsub(intro_text, "exactly/about", (is_exact and "exactly" or "about"))
	intro_text = string.gsub(intro_text, "''s''", string.format("%.3f", edstep_size))
	intro_text = string.gsub(intro_text, "''c''", cents)
	
	return intro_text
end

-- Parse function
function p.parse(unparsed)
	local unparsed = unparsed or "12"
	
	-- If the unparsed ed is only a numeric value, default to edo
	if tonumber(unparsed) ~= nil then
		unparsed = unparsed .. "edo"
	end
	
	-- Parse the step count, the suffix, and the equave
	local steps, equave = unparsed:match('^(%d+)[Ee][Dd](.+)$')
	
	-- Determine if the ed is for a cent value
	local is_cents = string.match(equave, '%d*%.?%d+[Cc¢]$') ~= nil
	
	-- Parse equave
	local parsed_equave
	local ed_type = ED_TYPE_DEFAULT
	if equave == "o" or equave == "O" or equave == "2" or equave == "2/1" then
		-- Equave is octave
		parsed_equave = 2
		ed_type = ED_TYPE_EDO
	elseif equave == "t" or equave == "T" or equave == "3" or equave == "3/1" then
		-- Equave is tritave/twelfth
		parsed_equave = 3
		ed_type = ED_TYPE_EDT
	elseif equave == "f" or equave == "F" or equave == "3/2" then
		-- Equave is fifth
		parsed_equave = rat.new(3,2)
		ed_type = ED_TYPE_EDF
	elseif string.match(equave, '^%d+$') ~= nil then
		-- Equave is arbitrary harmonic (not 2/1 or 3/1)
		parsed_equave = tonumber(equave)
		ed_type = ED_TYPE_EDH
	elseif string.match(equave, '^%d+/%d+$') ~= nil then
		-- Equave is arbitrary ratio (not 3/2)
		local num, den = equave:match('^(%d+)/(%d+)$')
		parsed_equave = rat.new(tonumber(num), tonumber(den))
		ed_type = ED_TYPE_EDR
	elseif is_cents then
		-- Equave is arbitrary cent value
		parsed_equave = tonumber(equave:match('^(%d*%.?%d+)[Cc¢]$'))
		ed_type = ED_TYPE_EDC
	else
		-- Equave is unsupported
		parsed_equave = 0
	end
	
	return tonumber(steps), parsed_equave, ed_type
end

-- ED-EDO comparison
-- Meant for finding what edo a nonoctave ed corresponds to
function p.edo_compare(ed)
	local ed = ed or "12edf"
	
	local parsed_ed, parsed_equave, ed_type = p.parse(ed)
	local edstep_size = 0;
	if ed_type == ED_TYPE_EDC then
		edstep_size = parsed_equave / parsed_ed
	else
		edstep_size = rat.cents(parsed_equave) / parsed_ed
	end
	local ed_compare = 1200 / edstep_size
	
	local ed_compare_text = "''ed1'' roughly corresponds to ''ed2''edo, "
	
	-- Replace certain strings with the intended final versions
	ed_compare_text = string.gsub(ed_compare_text, "''ed1''", ed)
	ed_compare_text = string.gsub(ed_compare_text, "''ed2''", string.format("%.3f", ed_compare))
	
	local ed_floor = math.floor(ed_compare)
	
	local diff = ed_compare - ed_floor
	if diff > 0.5 then
		ed_compare_text = ed_compare_text .. string.format("and can be seen as %dedo with stretched octaves", ed_floor+1)
	else
		ed_compare_text = ed_compare_text .. string.format("and can be seen as %dedo with compressed octaves", ed_floor)
	end
	
	if diff > 0.4 and diff < 0.6 then
		ed_compare_text = ed_compare_text .. string.format(", or roughly every second step of %dedo.", 2*ed_floor+1)
	else
		ed_compare_text = ed_compare_text .. "."
	end
	
	return ed_compare_text
	
end

-- Primary function
function p._ed_intro(ed)
	local ed = ed or "12"
	
	local parsed_ed, parsed_equave, ed_type = p.parse(ed)
	
	if ed_type == ED_TYPE_DEFAULT then
		return "Equave type not supported or entered incorrectly."
	elseif ed_type == ED_TYPE_EDO then
		return p.edo_intro(parsed_ed)
	elseif ed_type == ED_TYPE_EDT then
		return p.edt_intro(parsed_ed)
	elseif ed_type == ED_TYPE_EDF then
		return p.edf_intro(parsed_ed)
	elseif ed_type == ED_TYPE_EDH then
		return p.edh_intro(parsed_ed, parsed_equave)
	elseif ed_type == ED_TYPE_EDR then
		return p.edr_intro(parsed_ed, parsed_equave)
	elseif ed_type == ED_TYPE_EDC then
		return p.edc_intro(parsed_ed, parsed_equave)
	end
end

-- Tester function
function p.ed_intro_tester()
	return
		p._ed_intro("12") .. "\n" ..
		p._ed_intro("12edt") .. "\n" ..
		p._ed_intro("12edf") .. "\n" ..
		p._ed_intro("12ed4") .. "\n" ..
		p._ed_intro("12ed9/8") .. "\n" ..
		p._ed_intro("12ed666c") .. "\n" .. "\n" ..
		p._ed_intro("1") .. "\n" ..
		p._ed_intro("1ed3") .. "\n" ..
		p._ed_intro("1ed3/2") .. "\n" ..
		p._ed_intro("1ed4") .. "\n" ..
		p._ed_intro("1ed9/8") .. "\n" ..
		p._ed_intro("1ed666c") .. "\n"
end

-- Wrapper function; for use with a template
function p.ed_intro(frame)
	local ed = frame.args['ED']
	local edo = frame.args['EDO']		-- For backwards compatibility with edo intro

	if edo ~= nil then
		return p._ed_intro(edo)
	else
		return p._ed_intro(ed)
	end
end

return p