Module:JI ratios: Difference between revisions
merge some helper functions; adopt is_within_int_limit function; comments |
m comments |
||
| (18 intermediate revisions by 2 users not shown) | |||
| Line 1: | Line 1: | ||
-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]] | |||
local getArgs = require("Module:Arguments").getArgs | |||
local med = require("Module:Mediants") | |||
local rat = require("Module:Rational") | local rat = require("Module:Rational") | ||
local tip = require("Module:Template input parse") | |||
local utils = require("Module:Utils") | local utils = require("Module:Utils") | ||
local yesno = require("Module:Yesno") | local yesno = require("Module:Yesno") | ||
local p = {} | |||
-- Template for handling multiple entry of JI ratios into a template, and for | -- Template for handling multiple entry of JI ratios into a template, and for | ||
-- searching for JI ratios if automatic entry is desired. | -- searching for JI ratios if automatic entry is desired. | ||
-- This is a successor/replacement for JI ratio finder. | -- This is a successor/replacement for JI ratio finder. | ||
-- TODO: Refactor code such that: | |||
-- - For int-limit search, int limit is the first arg, and equave and min/max | |||
-- cents default to 2/1, 0c, and 1200c respectively. | |||
-- (int_limit, equave) | |||
-- (int_limit, min_cents, max_cents) | |||
-- - For odd-limit search, odd limit is the first arg, int limit defaults to | |||
-- twice the odd limit, and equave and min/max cents default to 2/1, 0c, and | |||
-- 1200c respectively. | |||
-- (odd_limit, int_limit, equave) | |||
-- (odd_limit, int_limit, min_cents, max_cents) | |||
-- - For prime-limit search, prime-limit is the first arg, int limit defaults to | |||
-- twice the largest prime, and equave and min/max cents default to 2/1, 0c, | |||
-- and 1200c respectively. | |||
-- (prime_limit, int_limit, equave) | |||
-- (prime_limit, int_limit, min_cents, max_cents) | |||
-- - For subgroup search, subgroup is the first arg, there's no default value | |||
-- for int limit (due to complexity of subgroups), and equave and min/max | |||
-- cents default to 2/1, 0c, and 1200c respectively. | |||
-- (subgroup, int_limit, equave) | |||
-- (subgroup, int_limit, min_cents, max_cents) | |||
-- - Filter ratios function is split into two: | |||
-- - Filter ratios by complement removes ratios from a table if its complement | |||
-- is missing. Complements are octave-complements by default. | |||
-- - Filter ratios by tenney height removes ratios from a table if its tenney | |||
-- height exceeds a passed-in value. | |||
-- TODO: write filter function for cent range | |||
-- Module searches for ratios that are, at the minimum, up to an equave and are | -- Module searches for ratios that are, at the minimum, up to an equave and are | ||
-- up to some integer limit. Search hierarchy is as follows: | -- up to some integer limit. Search hierarchy is as follows: | ||
-- - Search by subgroup ( | -- - Search by subgroup (subgroup elements may be nonprime or rational) | ||
-- - Then search by prime limit | -- - Then search by prime limit | ||
-- - Then search by odd limit | -- - Then search by odd limit | ||
-- - Then search by int limit | -- - Then search by int limit | ||
| Line 29: | Line 55: | ||
-- be omitted by Tenney height, or if no Tenney height is entered, omits | -- be omitted by Tenney height, or if no Tenney height is entered, omits | ||
-- ratios whose complements are missing. | -- ratios whose complements are missing. | ||
local DEFAULT_EQUAVE = rat.new(2) | |||
local DEFAULT_INT_LIMIT = 30 | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
| Line 37: | Line 66: | ||
-- Filters currently include: | -- Filters currently include: | ||
-- - Removing ratios that exceed a max Tenney height. | -- - Removing ratios that exceed a max Tenney height. | ||
-- - Removing ratios whose complement would exceed a max Tenney height | -- - Removing ratios whose complement would exceed a max Tenney height or int limit | ||
function p.filter_ratios(ratios, equave, int_limit, tenney_height, complements_only) | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
local complement = rat.mul(rat.inv(ratios[i]), equave) | |||
local ratio_th = rat.tenney_height(ratios[i]) | |||
local compl_th = rat.tenney_height(complement) | |||
function p. | -- Are the ratios within the Tenney height? | ||
-- Has no effect (defaults to TRUE) if Tenney height is infinity. | |||
local ratio_within_th = ratio_th <= tenney_height | |||
local compl_within_th = compl_th <= tenney_height | |||
-- Is the ratio's complement within the int limit? | |||
local compl_within_int_limit = rat.is_within_int_limit(complement, int_limit) | |||
if complements_only then | |||
if ratio_within_th and compl_within_th and compl_within_int_limit then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
else | |||
if ratio_within_th then | |||
table.insert(filtered_ratios, ratios[i]) | table.insert(filtered_ratios, ratios[i]) | ||
end | end | ||
end | end | ||
end | end | ||
return filtered_ratios | |||
end | end | ||
-- | -- Filters ratios from a table of ratios, returning an array of ratios within | ||
-- | -- the cent range and preserving the original table. Meant for searching for | ||
function p. | -- multiple ranges. TODO: write | ||
function p.filter_ratios_within_cent_range(ratios, min_cents, max_cents) | |||
end | end | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
| Line 79: | Line 108: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Int limit search finds ratios from 1/1 to an equave | -- Int limit search finds ratios from 1/1 to an equave, where each ratio's | ||
-- numerator or denominator don't exceed the int limit. | |||
function p.search_by_int_limit(equave, int_limit) | function p.search_by_int_limit(equave, int_limit) | ||
return p.search_by_int_limit_within_cents(0, rat.cents(equave), int_limit) | |||
end | |||
-- Cent range search finds ratios within a cent range. Meant for searching for | |||
-- ratios within a single interval range. If searching for ratios within many | |||
-- interval ranges, then try a broad search first. | |||
function p.search_by_int_limit_within_cents(min_cents, max_cents, int_limit) | |||
local init_ratios = {{1,1}, {1,0}} | local init_ratios = {{1,1}, {1,0}} | ||
local ratios = med. | local ratios = med.find_only_mediants(init_ratios, 2) | ||
for i = 3, int_limit do | |||
ratios = med.find_mediants_by_int_limit(ratios, i) | |||
-- Purge ratios from the beginning. | |||
-- If the first and second ratio are smaller than min_cents, and smaller | |||
-- than max_cents, then remove the first ratio. Keeping the first ratio | |||
-- would add mediants outside the cent range. | |||
local cents_1 = utils.log2(ratios[1][1] / ratios[1][2]) * 1200 | |||
local cents_2 = utils.log2(ratios[2][1] / ratios[2][2]) * 1200 | |||
if cents_1 < min_cents and cents_2 <= min_cents and cents_1 < max_cents and cents_2 < max_cents then | |||
table.remove(ratios, 1) | |||
end | |||
-- Purge ratios from the end. | |||
-- If the 2nd-last ratio and last ratio are greater than max_cents, and | |||
-- larger than min_cents, then remove the last ratio. Keeping the last | |||
-- ratio would add mediants outside the cent range. | |||
local cents_3 = utils.log2(ratios[#ratios-1][1] / ratios[#ratios-1][2]) * 1200 | |||
local cents_4 = utils.log2(ratios[#ratios ][1] / ratios[#ratios ][2]) * 1200 | |||
if cents_3 > max_cents and cents_4 >= max_cents and cents_3 > min_cents and cents_4 > min_cents then | |||
table.remove(ratios, #ratios) | |||
end | |||
end | |||
-- Convert to ratios that Module:Rational can work with | -- Convert to ratios that Module:Rational can work with | ||
| Line 97: | Line 150: | ||
end | end | ||
-- Remove ratios that | -- Remove any remaining ratios that fall outside the cent range. | ||
while rat.cents(ratios[1]) < min_cents do | |||
table.remove(ratios, 1) | |||
while rat. | end | ||
while rat.cents(ratios[#ratios]) > max_cents do | |||
table.remove(ratios, #ratios) | table.remove(ratios, #ratios) | ||
end | end | ||
| Line 111: | Line 165: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- | -- Convert odd limit into equivalent subgroup. | ||
-- EG, 11-odd-limit becomes 2.3.5.7.9.11 | |||
-- 2 is part of the subgroup by definition. | |||
function p.odd_limit_to_subgroup(odd_limit) | |||
local subgroup = { rat.new(2) } | |||
for i = 3, odd_limit, 2 do | |||
table.insert(subgroup, rat.new(i)) | |||
end | |||
return subgroup | |||
end | |||
function p.search_by_odd_limit(equave, int_limit, odd_limit) | |||
local subgroup = p.odd_limit_to_subgroup(odd_limit) | |||
return p.search_by_subgroup_within_cents(0, rat.cents(equave), int_limit, subgroup) | |||
end | |||
function p.search_by_odd_limit_within_cents(min_cents, max_cents, odd_limit) | |||
local subgroup = p.odd_limit_to_subgroup(odd_limit) | |||
return p.search_by_subgroup_within_cents(min_cents, max_cents, int_limit, subgroup) | |||
end | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
| Line 117: | Line 190: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Convert prime limit into equivalent subgroup. | |||
-- EG, 11-prime-limit becomes 2.3.5.7.11 | |||
function p.prime_limit_to_subgroup(prime_limit) | |||
local subgroup = {} | |||
for i = 3, prime_limit do | |||
local | |||
for i = | |||
local is_prime = true | local is_prime = true | ||
for j = 2, math.floor(math.sqrt(i)) do | for j = 2, math.floor(math.sqrt(i)) do | ||
| Line 134: | Line 203: | ||
end | end | ||
if is_prime then | if is_prime then | ||
table.insert( | table.insert(subgroup, rat.new(i)) | ||
end | end | ||
end | end | ||
return subgroup | |||
end | |||
return p. | |||
-- Prime limit search finds ratios with prime factors that don't exceed some | |||
-- prime limit. | |||
-- Upper bounds for searching is the equave and int limit. | |||
function p.search_by_prime_limit(equave, int_limit, prime_limit) | |||
local subgroup = p.prime_limit_to_subgroup(prime_limit) | |||
return p.search_by_subgroup_within_cents(0, rat.cents(equave), int_limit, subgroup) | |||
end | |||
-- Prime limit search finds ratios with prime factors that don't exceed some | |||
-- prime limit. Searches within a cent range. | |||
function p.search_by_prime_limit_within_cents(min_cents, max_cents, int_limit, prime_limit) | |||
local subgroup = p.prime_limit_to_subgroup(prime_limit) | |||
local ratios = p.search_by_subgroup_within_cents(min_cents, max_cents, int_limit, subgroup) | |||
while rat.cents(ratios[1]) < min_cents do | |||
table.remove(ratios, 1) | |||
end | |||
return ratios | |||
end | end | ||
| Line 146: | Line 232: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Subgroup search find ratios that are products of at least two non-unique | |||
-- elements from the subgroup. | |||
function p.search_by_subgroup(equave, int_limit, subgroup) | function p.search_by_subgroup(equave, int_limit, subgroup) | ||
local equave = equave or rat.new(2,1) -- Defualt equave is 2/1. | local ratios = p.search_by_subgroup_within_cents(0, rat.cents(equave), int_limit, subgroup) | ||
local int_limit = int_limit or 50 -- Default is 50 | return ratios | ||
local subgroup = subgroup or {rat.new(2), rat.new(3), rat.new(7)} -- Default is 2.3.7 subgroup | end | ||
function p.search_by_subgroup_within_cents(min_cents, max_cents, int_limit, subgroup) | |||
--local equave = equave or rat.new(2,1) -- Defualt equave is 2/1. | |||
--local int_limit = int_limit or 50 -- Default is 50 | |||
--local subgroup = subgroup or {rat.new(2), rat.new(3), rat.new(7)} -- Default is 2.3.7 subgroup | |||
-- Find all possible ways to multiply subgroup elements with one another | -- Find all possible ways to multiply subgroup elements with one another | ||
| Line 189: | Line 282: | ||
for j = i, #products do | for j = i, #products do | ||
local new_ratio = rat.div(products[j], products[i]) | local new_ratio = rat.div(products[j], products[i]) | ||
if rat. | if rat.cents(new_ratio) > max_cents then break end | ||
if not p.find_ratio_in_table(new_ratios, new_ratio) and rat.is_within_int_limit(new_ratio, int_limit) then | if not p.find_ratio_in_table(new_ratios, new_ratio) and rat.is_within_int_limit(new_ratio, int_limit) then | ||
| Line 202: | Line 295: | ||
-- Sort | -- Sort | ||
table.sort(ratios, rat.lt) | table.sort(ratios, rat.lt) | ||
-- Remove ratios less than minimum | |||
while rat.cents(ratios[1]) < min_cents do | |||
table.remove(ratios, 1) | |||
end | |||
return ratios | return ratios | ||
end | end | ||
-------------------------------------------------------------------------------- | |||
------------------------------- HELPER FUNCTIONS ------------------------------- | |||
-------------------------------------------------------------------------------- | |||
-- Heleper function; merges elements from source table with destination table | -- Heleper function; merges elements from source table with destination table | ||
| Line 258: | Line 360: | ||
end | end | ||
return texts | return texts | ||
end | |||
-------------------------------------------------------------------------------- | |||
---------------------------- ARG-PARSING FUNCTION ------------------------------ | |||
-------------------------------------------------------------------------------- | |||
-- Parse search args if entered as one string. Use is to be determined. | |||
function p.parse_args(search_args) | |||
local parsed = tip.parse_kv_pairs(search_args) | |||
if parsed["Equave"] ~= nil then | |||
parsed["Equave"] = rat.parse(parsed["Equave"]) | |||
end | |||
if parsed["Int Limit"] ~= nil then | |||
parsed["Int Limit"] = tonumber(parsed["Int Limit"]) | |||
end | |||
if parsed["Tenney Height"] ~= nil then | |||
parsed["Tenney Height"] = tonumber(parsed["Tenney Height"]) | |||
end | |||
if parsed["Prime Limit"] ~= nil then | |||
parsed["Prime Limit"] = tonumber(parsed["Prime Limit"]) | |||
end | |||
if parsed["Subgroup"] ~= nil then | |||
local subgroup_elements = tip.parse_numeric_pairs(parsed["Subgroup"], ".", "/", true) | |||
for i = 1, #subgroup_elements do | |||
subgroup_elements[i] = rat.new(subgroup_elements[i][1], subgroup_elements[i][2]) | |||
end | |||
parsed["Subgroup"] = subgroup_elements | |||
end | |||
if parsed["Complements Only"] ~= nil then | |||
parsed["Complements Only"] = yesno(parsed["Complements Only"]) | |||
end | |||
return parsed | |||
end | end | ||
| Line 265: | Line 406: | ||
-- Function callable by other modules | -- Function callable by other modules | ||
-- Ratios are returned as a table, for use with other modules | -- Ratios are returned as a table, for use with other modules. | ||
function p._ji_ratios(args) | function p._ji_ratios(args) | ||
-- Args for ease of access | -- Args for ease of access | ||
equave = args["Equave"] | equave = args["Equave" ] or DEFAULT_EQUAVE | ||
int_limit = args["Int Limit"] | int_limit = args["Int Limit" ] or DEFAULT_INT_LIMIT | ||
odd_limit = args["Odd Limit"] | odd_limit = args["Odd Limit" ] | ||
prime_limit = args["Prime Limit"] | prime_limit = args["Prime Limit"] | ||
subgroup = args["Subgroup"] | subgroup = args["Subgroup" ] | ||
-- Filtering args | |||
tenney_height = args["Tenney Height" ] or 1/0 -- Default Tenney height is infinity | |||
complements_only = args["Complements Only"] or false -- Default is to include all ratios | |||
local ratios = {} | local ratios = {} | ||
| Line 282: | Line 427: | ||
ratios = p.search_by_int_limit(equave, int_limit) | ratios = p.search_by_int_limit(equave, int_limit) | ||
end | end | ||
-- Filter ratios | |||
ratios = p.filter_ratios(ratios, equave, int_limit, tenney_height, complements_only) | |||
return ratios | return ratios | ||
| Line 287: | Line 435: | ||
-- Invokable function; for templates | -- Invokable function; for templates | ||
-- Ratios are returned as a comma-delimited list | -- Ratios are returned as a comma-delimited list. For finer control, it's | ||
-- necessary to call the "main" function, then further process the results. | |||
function p.ji_ratios(frame) | function p.ji_ratios(frame) | ||
args = getArgs(frame) | args = getArgs(frame) | ||
| Line 294: | Line 443: | ||
-- Ratios are searched from 1/1 to some equave (default 2/1), so an equave | -- Ratios are searched from 1/1 to some equave (default 2/1), so an equave | ||
-- must be passed in. | -- must be passed in. | ||
args["Equave"] = args["Equave"] ~= nil and rat.parse(args["Equave"] | args["Equave"] = args["Equave"] ~= nil and rat.parse(args["Equave"]) | ||
-- Preprocess int limit | -- Preprocess int limit | ||
-- Ratios are searched up to some int limit (default 50), so an int limit | -- Ratios are searched up to some int limit (default 50), so an int limit | ||
-- must be passed in. | -- must be passed in. | ||
args["Int Limit"] = args["Int Limit"] ~= nil and tonumber(args["Int Limit"]) | args["Int Limit"] = args["Int Limit"] ~= nil and tonumber(args["Int Limit"]) | ||
-- Preprocess Tenney height | -- Preprocess Tenney height | ||
| Line 325: | Line 474: | ||
-- Find and return ratios | -- Find and return ratios | ||
local result = p.ratios_as_string(p._ji_ratios(args)) | |||
return | local debugg = yesno(frame.args["debug"]) | ||
if debugg == true then | |||
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>" | |||
end | |||
return frame:preprocess(result) | |||
end | end | ||
function p.tester() | function p.tester() | ||
return p.ratios_as_string(p. | --return p.ratios_as_string(p._ji_ratios(p.parse_args("Int Limit: 16; Equave: 3/1; Complements Only: 0"))) | ||
--return p.ratios_as_string(p.search_by_prime_limit_within_cents(372, 440, 17, 30)) | |||
return p.ratios_as_string(p.search_by_odd_limit(rat.new(2), 15, 15*2)) | |||
end | end | ||