Module:JI ratios in ED

From Xenharmonic Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:JI ratios in ED/doc

local utils = require('Module:Utils')
local interval = require('Module:Interval')
local rat = require('Module:Rational')
local jiraf = require('Module:JI ratio finder')
local tip = require('Module:Template input parse')
local et = require('Module:ET')
local p = {}

-- Helper function
-- Given a prime limit, return an array of all primes between (and including) 2
-- and that limit
-- EG, 7-limit becomes { 2, 3, 5, 7 }
function p.primes_within_prime_limit(prime_limit)
	local prime_limit = prime_limit or 7
	
	local primes = {}
	for i = 2, prime_limit do
		if utils.is_prime(i) then
			table.insert(primes, i)
		end
	end
	return primes
end

-- Helper function
-- Converts a set of primes (typically a prime subgroup) into text
function p.subgroup_as_text(primes)
	local primes = primes or { 2, 3, 5, 7 }
	
	local subgroup = ""
	for i = 1, #primes do
		if i ~= #primes then
			subgroup = subgroup .. string.format("%d.", primes[i])
		else
			subgroup = subgroup .. primes[i]
		end
	end
	return subgroup
end

-- Helper function
-- Creates the non-header cells in the table
function p.ji_ratio_in_ed_content_cells(steps, equave, candidate_ratios, primes, tolerance)
	local steps = steps or 12
	local equave = equave or rat.new(2)
	local candidate_ratios = candidate_ratios or { rat.new(1), rat.new(2), rat.new(3, 2), rat.new(4, 3), rat.new (5, 4), rat.new (9, 7)}
	local primes = primes or { 2, 3, 7 }
	local tolerance = tolerance or 20
	
	-- Calculate equave
	local equave_in_cents = rat.cents(equave)
	
	-- Build the rows for each step, showing ratios by limit
	local result = ""
	for i = 1, steps + 1 do
		local step = i - 1
		
		-- Table headers
		local step_in_cents = (step / steps) * rat.cents(equave)
		result = result .. string.format('| %d\n', step)
		result = result .. string.format('| %.3f\n', step_in_cents)
		
		-- If this is the first or last step, only use the unison or equave
		-- respectively; otherwise, filter ratios by whether they're within the
		-- current step
		local filtered_ratios = {}
		if step == 0 then
			filtered_ratios = { rat.new(1) }
		elseif step == steps then
			filtered_ratios = { equave }
		else 
			filtered_ratios = jiraf.filter_ratios_by_range(candidate_ratios, step_in_cents - tolerance, step_in_cents + tolerance)
		end
		
		-- Add ratios according to harmonic class
		for j = 1, #primes do
			local current_prime = primes[j]
			
			-- For each prime, filter by harmonic class
			-- For the first column, filter by prime limit instead
			local prime_filtered_ratios = {}
			if j == 1 then
				prime_filtered_ratios = jiraf.filter_ratios_by_prime_limit(filtered_ratios, primes[j])
			else
				prime_filtered_ratios = jiraf.filter_ratios_by_harmonic_class(filtered_ratios, primes[j])	
			end

			local ratios_as_text = jiraf.ratios_to_text(prime_filtered_ratios, "<br>", true)
			result = result .. string.format('| %s\n', ratios_as_text)
		end
		
		result = result .. string.format('|-\n')
	end
	return result	
end

-- Main function variant for prime limit
function p.ji_ratios_in_ed_by_prime_limit(input_et, int_limit, prime_limit, tenney_height, threshold)
	local input_et = input_et or et.parse("12edo")
	local prime_limit = prime_limit or 7
	local tenney_height = tenney_height or 10
	local int_limit = int_limit or 99
	local threshold = threshold or 0.3
	
	-- Get the number of divisions, equave, and et as text (eg edo, edt, etc)
	local steps = input_et['size']
	local equave = input_et['equave']
	if tonumber(equave) ~= nil then
		equave = rat.new(equave)
	end
	local et_as_string = et.as_string(input_et)
	
	-- Calculate equave and tolerance
	-- Tolerance is a percentage (threshold) of the step size
	local equave_in_cents = rat.cents(equave)
	local tolerance = equave_in_cents / steps * threshold

	-- Calculate candidate ratios
	-- Then filter based on whether their complements exceed the int limit
	-- Then filter candidate ratios by tenney height (excludes equave factors)
	local candidate_ratios = jiraf.find_candidate_ratios_within_prime_limit(equave_in_cents, int_limit, prime_limit)
	candidate_ratios = jiraf.filter_ratios_by_equave_complement_int_limit(candidate_ratios, int_limit, equave)
	candidate_ratios = jiraf.filter_ratios_by_no_equave_factors_tenney_height(candidate_ratios, tenney_height, equave)
	
	-- Get the primes
	local primes = p.primes_within_prime_limit(prime_limit)
	
	-- Build table headers
	local result = string.format('{| class="wikitable center-all"\n')
	result = result .. string.format('|+ Intervals of %s (as a %d-limit temperament)\n', et_as_string, prime_limit)
	result = result .. string.format('|-\n')
	result = result .. string.format('! rowspan="2" | [[Degree]]\n')
	result = result .. string.format('! rowspan="2" | [[Cent]]s\n')
	result = result .. string.format('! colspan="%d" | Approximated [[JI]] intervals\n', #primes)
	result = result .. string.format('|-\n')
	
	-- Build header cells for each prime limit
	for i = 1, #primes do
		result = result .. string.format('! [[%d-limit]]\n', primes[i])
	end
	result = result .. string.format('|-\n')
	
	-- Add rest of table
	result = result .. p.ji_ratio_in_ed_content_cells(steps, equave, candidate_ratios, primes, tolerance)
	
	result = result .. string.format('|}\n')
	return result
end

-- Main function variant for prime subgroup
function p.ji_ratios_in_ed_by_prime_subgroup(input_et, int_limit, primes, tenney_height, threshold)
	local input_et = input_et or et.parse("12edo")
	local primes = primes or { 2, 3, 5, 11 }
	local tenney_height = tenney_height or 10
	local int_limit = int_limit or 99
	local threshold = threshold or 0.3
	
	-- Get the number of divisions, equave, and et as text (eg edo, edt, etc)
	local steps = input_et['size']
	local equave = input_et['equave']
	if tonumber(equave) ~= nil then
		equave = rat.new(equave)
	end
	local et_as_string = et.as_string(input_et)
	
	-- Calculate equave and tolerance
	-- Tolerance is a percentage (threshold) of the step size
	local equave_in_cents = rat.cents(equave)
	local tolerance = equave_in_cents / steps * threshold
	
	-- Calculate candidate ratios
	-- Then filter based on whether their complements exceed the int limit
	-- Then filter candidate ratios by tenney height (excludes equave factors)
	local candidate_ratios = jiraf.find_candidate_ratios_within_subgroup(equave_in_cents, int_limit, primes)
	candidate_ratios = jiraf.filter_ratios_by_equave_complement_int_limit(candidate_ratios, int_limit, equave)
	candidate_ratios = jiraf.filter_ratios_by_no_equave_factors_tenney_height(candidate_ratios, tenney_height, equave)
	
	-- Get subgroup as text
	local subgroup_as_text = p.subgroup_as_text(primes)
	
	-- Build table headers
	local result = string.format('{| class="wikitable center-all"\n')
	result = result .. string.format('|+ Intervals of %s (as a %s subgroup temperament)\n', et_as_string, subgroup_as_text)
	result = result .. string.format('|-\n')
	result = result .. string.format('! rowspan="2" | [[Degree]]\n')
	result = result .. string.format('! rowspan="2" | [[Cent]]s\n')
	result = result .. string.format('! colspan="%d" | Approximated [[JI]] intervals\n', #primes)
	result = result .. string.format('|-\n')
	
	-- Build header cells for each prime limit
	for i = 1, #primes do
		result = result .. string.format('! [[%d-limit]]\n', primes[i])
	end
	result = result .. string.format('|-\n')
	
	-- Add rest of table
	result = result .. p.ji_ratio_in_ed_content_cells(steps, equave, candidate_ratios, primes, tolerance)
	
	result = result .. string.format('|}\n')
	return result
end

-- Wrapper function for primary function; to be called by template
function p.ji_ratios_in_ed_frame(frame)
	
	-- Parse the ed; if it's just a number, interpret it as an edo
	local input_et_unparsed = frame.args["ED"] or 12
	if tonumber(input_et_unparsed) ~= nil then
		input_et_unparsed = input_et_unparsed .. "edo"
	end
	local input_et = et.parse(input_et_unparsed)
	
	local tenney_height = tonumber(frame.args["Tenney Height"]) or 10
	local int_limit = tonumber(frame.args["Integer Limit"]) or 99
	local threshold = tonumber(frame.args["Threshold"]) or 0.3

	local primes = tonumber(frame.args["Prime Limit"]) or 5
	local temperament_type = "Prime Limit"
	if string.len(frame.args["Subgroup"]) > 0 then 
		primes = tip.parse_numeric_entries(frame.args["Subgroup"], '.') or tip.parse_numeric_entries(frame.args["Subgroup"], ',')
		temperament_type = "Subgroup"
	end
	
	local result = ""
	if temperament_type == "Subgroup" then
		result = p.ji_ratios_in_ed_by_prime_subgroup(input_et, int_limit, primes, tenney_height, threshold)
	elseif temperament_type == "Prime Limit" then
		result = p.ji_ratios_in_ed_by_prime_limit(input_et, int_limit, primes, tenney_height, threshold)
	end
	
	return result
end

return p