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 into a subgroup
function p.primes_as_subgroup(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
-- Given a set of primes, find its temperament interpretation; IE, is it a
-- prime subgroup temperament (EG, 2.3.7) or a prime-limit temperament (EG,
-- 7-limit)?
-- If the set of primes is a prime limit, then it should contain all the primes
-- between 2 (inclusive) and the largest prime (inclusive). If it skips any
-- primes, then it's a subgroup.
function p.primes_as_temperament_interpretation(primes)
local primes = primes or { 2, 3, 5, 7 }
local primes_to_compare = p.primes_within_prime_limit(primes[#primes])
local temperament_interpretation = ""
if p.primes_as_subgroup(primes) == p.primes_as_subgroup(primes_to_compare) then
temperament_interpretation = string.format("%d-limit", primes[#primes])
else
temperament_interpretation = p.primes_as_subgroup(primes) .. " subgroup"
end
return temperament_interpretation
end
-- Primary function
function p.find_ratios_in_ed(input_et, primes, tenney_height, denominator_limit)
local input_et = input_et or et.parse("12edo")
local primes = primes or { 2, 3, 5, 7 }
local tenney_height = tenney_height or 10
local denominator_limit = denominator_limit or 99
-- Get the number of divisions, equave, and et as text (eg edo, edt, etc)
local steps = input_et['size']
local equave = input_et['equave']
local et_as_string = et.as_string(input_et)
-- Calculate equave and tolerance
local equave_in_cents = rat.cents(equave)
local tolerance = equave_in_cents / steps * 0.4
-- Calculate temperament interpretation
local temperament_interpretation = p.primes_as_temperament_interpretation(primes)
-- Find candidate ratios; filter later
local max_prime = primes[#primes]
local candidate_ratios = jiraf.find_candidate_ratios_within_subgroup(equave_in_cents, denominator_limit, primes)
-- Build table headers
local result = string.format('{| class="wikitable center-all"\n')
result = result .. string.format('|+ Intervals of %s (as a %s temperament)\n', et_as_string, temperament_interpretation)
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 ([[error]] in cents)\n', #primes)
result = result .. string.format('|-\n')
-- Add table headers for prime limits (technically harmonic classes)
for i = 1, #primes do
local current_prime = primes[i]
result = result .. string.format('! [[%d-limit]]\n', current_prime)
end
result = result .. string.format('|-\n')
-- Build the rows for each step, showing ratios by limit
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)
filtered_ratios = jiraf.filter_ratios_by_range(candidate_ratios, step_in_cents - tolerance, step_in_cents + tolerance)
-- Add ratios according to harmonic class
for j = 1, #primes do
local current_prime = primes[j]
local prime_filtered_ratios = {}
-- Override filtered ratios denpending on whether the ratio is the
-- unison or equave
if step == 0 and j == 1 then
prime_filtered_ratios = { rat.new(1) }
elseif step == steps then
prime_filtered_ratios = jiraf.filter_ratios_by_harmonic_class({ equave }, current_prime)
else
-- Filter ratios by harmonic class, then by complement-agnostic
-- Tenney height
prime_filtered_ratios = jiraf.filter_ratios_by_harmonic_class(filtered_ratios, current_prime)
prime_filtered_ratios = jiraf.filter_ratios_by_complement_agnostic_tenney_height(prime_filtered_ratios, tenney_height, equave)
end
-- Add ratios to cells
local ratios_as_text = jiraf.ratios_to_text_with_error(prime_filtered_ratios, step_in_cents, "<br>", true)
result = result .. string.format('| %s\n', ratios_as_text)
end
result = result .. string.format('|-\n')
end
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)
local input_et = et.parse(frame.args["ED"])
if tonumber(et) ~= nil then
input_et = input_et .. "edo"
end
local primes = { 2, 3, 5, 7 }
if string.len(frame.args["Subgroup"]) > 0 then
primes = p.primes_within_prime_limit(tonumber(frame.args["Prime Limit"]))
elseif string.len(frame.args["Prime Limit"]) > 0 then
primes = tip.parse_numeric_entries(frame.args["Subgroup"], '.') or tip.parse_numeric_entries(frame.args["Subgroup"], ',')
end
local tenney_height = tonumber(frame.args["Tenney Height"]) or 10
local denominator_limit = tonumber(frame.args["Denominator Limit"]) or 99
local result = p.find_ratios_in_ed(input_et, primes, tenney_height, denominator_limit)
return result
end
return p