Module:JI ratio finder: Difference between revisions
m find_ratio_for_cents now returns ratios (as lua tables), not text |
ArrowHead294 (talk | contribs) m Alphabetise dependencies |
||
(23 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
local p = {} | |||
-- local interval = require('Module:Interval') | |||
local rat = require('Module:Rational') | |||
local utils = require('Module:Utils') | local utils = require('Module:Utils') | ||
-- | -- Finds the Tenney height of a ratio that ignores equave factors. | ||
-- Finds approximated JI ratios for a cent value for a prime and odd limit | -- If the equave is 2/1, then this is equivalent to no-2's Tenney Height. | ||
-- This is an attempt at generalizing no-2's Tenney height for nonoctave | |||
-- equaves, such as 3/1 or 3/2, which would be no-2's and no-2's-or-3's. | |||
function p.no_equave_factors_tenney_height(ratio, equave) | |||
local ratio = ratio or rat.new(81, 64) | |||
local equave = equave or rat.new(2) | |||
local ratio_copy = rat.copy(ratio) | |||
for key, value in pairs(equave) do | |||
if tonumber(key) ~= nil and ratio_copy[key] ~= nil then | |||
ratio_copy[key] = 0 | |||
end | |||
end | |||
return rat.tenney_height(ratio_copy) | |||
end | |||
-- Finds the equave complement of a ratio. | |||
-- For a ratio a/b and equave p/q, the equave complement of a/b is c/d, such | |||
-- that multiplying a/b and c/d equals p/q. In other words, c/d is p/q * b/a. | |||
function p.equave_complement(ratio, equave) | |||
local equave = equave or rat.new(2) | |||
return rat.mul(rat.inv(ratio), equave) | |||
end | |||
-- Determines whether a ratios is within a subgroup; for a subgroup p1.p2..pn, | |||
-- a ratio p/q is in that subgroup if its prime factorization contains any prime | |||
-- factors p1, p2, .. pn. | |||
function p.within_subgroup(ratio, subgroup) | |||
local ratio = ratio or rat.new(5, 2) | |||
local subgroup = subgroup or { 2, 3, 5 } | |||
local within_subgroup = "" | |||
for key, value in pairs(ratio) do | |||
if key ~= "sign" then | |||
within_subgroup = within_subgroup and utils.table_contains(subgroup, key) | |||
end | |||
end | |||
return within_subgroup | |||
end | |||
-- Finds candidate ratios up to a cent value and up to an integer limit that | |||
-- applies to the denominator only, and within a prime limit. | |||
-- Ratios found this way will range from 0 cents to the given cent value. | |||
-- These ratios should then be filtered as needed. | |||
function p.find_candidate_ratios_within_prime_limit(cents, int_limit, prime_limit) | |||
local cents = cents or 1200 | |||
local int_limit = int_limit or 99 | |||
local prime_limit = prime_limit or 97 | |||
local candidate_ratios = {} | |||
for i = 1, int_limit do | |||
for j = i, int_limit do | |||
local numerator = j | |||
local denominator = i | |||
-- Proceed if ratio is simplified | |||
if utils._gcd(numerator, denominator) == 1 then | |||
local current_ratio = rat.new(numerator, denominator) | |||
local is_within_prime_limit = rat.max_prime(current_ratio) <= prime_limit | |||
local is_within_cents = rat.cents(current_ratio) <= cents | |||
if is_within_cents and is_within_prime_limit then | |||
table.insert(candidate_ratios, current_ratio) | |||
end | |||
end | |||
end | |||
end | |||
return candidate_ratios | |||
end | |||
-- Finds candidate ratios up to a cent value, up to a denominator limit, and | |||
-- with any of the given prime factors | |||
function p.find_candidate_ratios_within_subgroup(cents, int_limit, primes) | |||
local cents = cents or 1200 | |||
local int_limit = int_limit or 99 | |||
local primes = primes or { 2, 3, 7, 11 } | |||
local candidate_ratios = {} | |||
for i = 1, int_limit do | |||
for j = i, int_limit do | |||
local numerator = j | |||
local denominator = i | |||
-- Proceed if ratio is simplified | |||
if utils._gcd(numerator, denominator) == 1 then | |||
local current_ratio = rat.new(numerator, denominator) | |||
local is_within_subgroup = p.within_subgroup(current_ratio, primes) | |||
local is_within_cents = rat.cents(current_ratio) <= cents | |||
if is_within_cents and is_within_subgroup then | |||
table.insert(candidate_ratios, current_ratio) | |||
end | |||
end | |||
end | |||
end | |||
return candidate_ratios | |||
end | |||
-- Filter ratios based on whether they're within a cent range | |||
function p.filter_ratios_by_range(ratios, min_cents, max_cents) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local min_cents = min_cents or 380 | |||
local max_cents = max_cents or 420 | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
local ratio_in_cents = rat.cents(ratios[i]) | |||
if ratio_in_cents >= min_cents and ratio_in_cents <= max_cents then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Filter ratios based on whether its equave complement exceeds an int limit | |||
function p.filter_ratios_by_equave_complement_int_limit(ratios, int_limit, equave) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local int_limit = int_limit or 10 | |||
local equave = equave or rat.new(2) | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
local complement = p.equave_complement(ratios[i], equave) | |||
local numerator, denominator = rat.as_pair(complement) | |||
if numerator <= int_limit and denominator <= int_limit then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Filters ratios by prime limit | |||
-- Filters out ratios whose prime factorizations contains primes larger than | |||
-- the prime limit | |||
function p.filter_ratios_by_prime_limit(ratios, prime_limit) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local prime_limit = prime_limit or 5 | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
if rat.max_prime(ratios[i]) <= prime_limit then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Filters ratios by odd limit | |||
-- Filters out ratios where, ignoring powers of 2, either the numerator or | |||
-- denominator exceeds the odd limit | |||
function p.filter_ratios_by_odd_limit(ratios, odd_limit) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local odd_limit = odd_limit or 5 | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
if rat.odd_limit(ratios[i]) <= odd_limit then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Filters ratios by harmonic class | |||
-- Filters out ratios whose largest prime is larger than the given prime; only | |||
-- ratios whose largest prime is the given harmonic class are kept | |||
function p.filter_ratios_by_harmonic_class(ratios, harmonic_class) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local harmonic_class = harmonic_class or 5 | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
if rat.max_prime(ratios[i]) == harmonic_class then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Filters ratios by prime subgroup (such as 2.3.5) | |||
-- Filters out ratios whose factors are not in the given subgroup; this requires | |||
-- filtering by each prime as a harmonic class | |||
function p.filter_ratios_by_subgroup(ratios, subgroup) | |||
local ratios = ratios or { rat.new(1), rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local subgroup = subgroup or { 2, 3, 7 } | |||
local candidate_ratios = p.filter_ratios_by_prime_limit(ratios, subgroup[#subgroup]) | |||
local filtered_ratios = {} | |||
for i = 1, #subgroup do | |||
local prime_filtered_ratios = p.filter_ratios_by_harmonic_class(candidate_ratios, subgroup[i]) | |||
for j = 1, #prime_filtered_ratios do | |||
table.insert(filtered_ratios, prime_filtered_ratios[j]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Filters ratios by Tenney height | |||
-- Filters ratios where lg(numerator) + lg(denominator) does not exceed the | |||
-- given height, where lg is log-base-2 | |||
function p.filter_ratios_by_tenney_height(ratios, tenney_height) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local tenney_height = tenney_height or 5.0 | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
if rat.tenney_height(ratios[i]) <= tenney_height then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Filters ratios by no-equave-factors Tenney height | |||
-- Filters ratios where lg(numerator) + lg(denominator) does not exceed the | |||
-- given height, where lg is log-base-2. If the equave is 2/1, this is the same | |||
-- as no-2's tenney height. | |||
-- EG, assuming 2/1 equave, 3/2 and 4/3 have the same tenney height of lg(3). | |||
function p.filter_ratios_by_no_equave_factors_tenney_height(ratios, tenney_height, equave) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local tenney_height = tenney_height or 5.0 | |||
local equave = equave or rat.new(2) | |||
local filtered_ratios = {} | |||
for i = 1, #ratios do | |||
if p.no_equave_factors_tenney_height(ratios[i], equave) <= tenney_height then | |||
table.insert(filtered_ratios, ratios[i]) | |||
end | |||
end | |||
return filtered_ratios | |||
end | |||
-- Finds approximated JI ratios for a cent value for a prime and denominator limit | |||
-- Meant to find ratios within the range of a single cent value | |||
-- TODO: use integer limit instead of odd limit | |||
function p.find_ratios_for_cents(cents, tolerance, prime_limit, odd_limit) | function p.find_ratios_for_cents(cents, tolerance, prime_limit, odd_limit) | ||
local cents = cents or 700 | local cents = cents or 700 | ||
Line 59: | Line 310: | ||
end | end | ||
function p.ratios_to_text(ratios) | -- Converts ratios to text, with delimiter | ||
local ratios = ratios or { rat.new(5, 4), rat.new (81, 64) } | -- Default delimiter is a comma followed by a space | ||
function p.ratios_to_text(ratios, delimiter, add_links) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local delimiter = delimiter or ", " | |||
local add_links = add_links == true | |||
local text = "" | |||
if add_links then | |||
for i = 1, #ratios do | |||
text = text .. string.format("[[%s]]", rat.as_ratio(ratios[i])) | |||
if i < #ratios then | |||
text = text .. delimiter | |||
end | |||
end | |||
else | |||
for i = 1, #ratios do | |||
text = text .. rat.as_ratio(ratios[i]) | |||
if i < #ratios then | |||
text = text .. delimiter | |||
end | |||
end | |||
end | |||
return text | |||
end | |||
-- Converts ratios to text, with delimiter | |||
-- Default delimiter is a comma followed by a space | |||
function p.ratios_to_text_with_error(ratios, target_cents, delimiter, add_links) | |||
local ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | |||
local target_cents = target_cents or 400 | |||
local delimiter = delimiter or ", " | |||
local add_links = add_links == true | |||
local text = "" | local text = "" | ||
for i = 1, #ratios do | for i = 1, #ratios do | ||
text = text .. | local ratio_as_text = rat.as_ratio(ratios[i]) | ||
if i | local ratio_as_cents = rat.cents(ratios[i]) | ||
text = text .. | local diff = target_cents - ratio_as_cents | ||
if add_links then | |||
text = text .. string.format('[[%s]]', ratio_as_text) | |||
else | |||
text = text .. ratio_as_text | |||
end | |||
if diff > 0 then | |||
text = text .. string.format(' (+%.3f)', diff) | |||
elseif diff < 0 then | |||
text = text .. string.format(' (%.3f)', diff) | |||
elseif diff == 0 then | |||
text = text .. " (just)" | |||
end | |||
if i < #ratios then | |||
text = text .. delimiter | |||
end | end | ||
end | end | ||
return text | return text | ||
end | end | ||
return p | return p |