Module:JI ratio finder: Difference between revisions
m Fixed delimiter-adding code |
ArrowHead294 (talk | contribs) m Alphabetise dependencies |
||
(20 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 | -- Finds the Tenney height of a ratio that ignores equave factors. | ||
-- If the equave is 2/1, then this is equivalent to no-2's Tenney Height. | |||
-- the | -- 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) | ||
function p. | |||
local ratio = ratio or rat.new(81, 64) | local ratio = ratio or rat.new(81, 64) | ||
local equave = equave or rat.new(2) | local equave = equave or rat.new(2) | ||
local | |||
return | 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 | end | ||
Line 24: | Line 31: | ||
end | end | ||
-- Finds candidate ratios up to a cent value and up to an integer limit | -- Determines whether a ratios is within a subgroup; for a subgroup p1.p2..pn, | ||
-- Ratios found this way will range from 0 cents to the given cent value | -- a ratio p/q is in that subgroup if its prime factorization contains any prime | ||
-- These ratios should then be filtered as needed | -- factors p1, p2, .. pn. | ||
function p. | 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 cents = cents or 1200 | ||
local int_limit = int_limit or 99 | local int_limit = int_limit or 99 | ||
local prime_limit = prime_limit or 97 | |||
local candidate_ratios = {} | local candidate_ratios = {} | ||
for i = 1, int_limit do | for i = 1, int_limit do | ||
for j = i, int_limit do | |||
local numerator = j | |||
local denominator = i | |||
local | |||
if | -- 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 | |||
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 | |||
numerator = numerator | -- 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 | end | ||
Line 69: | Line 126: | ||
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 | return filtered_ratios | ||
end | end | ||
Line 117: | Line 191: | ||
if rat.max_prime(ratios[i]) == harmonic_class then | if rat.max_prime(ratios[i]) == harmonic_class then | ||
table.insert(filtered_ratios, ratios[i]) | 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 | ||
end | end | ||
Line 140: | Line 234: | ||
end | end | ||
-- Filters ratios by | -- Filters ratios by no-equave-factors Tenney height | ||
-- Filters ratios where lg(numerator) + lg(denominator) does not exceed the | -- Filters ratios where lg(numerator) + lg(denominator) does not exceed the | ||
-- given height, where lg is log-base-2 | -- 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. | 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 ratios = ratios or { rat.new(5, 4), rat.new(81, 64), rat.new(9, 7) } | ||
local tenney_height = tenney_height or 5.0 | local tenney_height = tenney_height or 5.0 | ||
Line 152: | Line 246: | ||
local filtered_ratios = {} | local filtered_ratios = {} | ||
for i = 1, #ratios do | for i = 1, #ratios do | ||
if p. | if p.no_equave_factors_tenney_height(ratios[i], equave) <= tenney_height then | ||
table.insert(filtered_ratios, ratios[i]) | table.insert(filtered_ratios, ratios[i]) | ||
end | end | ||
Line 160: | Line 254: | ||
end | end | ||
-- Finds approximated JI ratios for a cent value for a prime and | -- 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 | -- Meant to find ratios within the range of a single cent value | ||
-- TODO: use integer limit instead of odd limit | -- TODO: use integer limit instead of odd limit | ||
Line 218: | Line 312: | ||
-- Converts ratios to text, with delimiter | -- Converts ratios to text, with delimiter | ||
-- Default delimiter is a comma followed by a space | -- Default delimiter is a comma followed by a space | ||
function p.ratios_to_text(ratios, delimiter) | function p.ratios_to_text(ratios, delimiter, add_links) | ||
local ratios = ratios or { rat.new(5, 4), rat.new (81, 64) } | 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 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]) | ||
local ratio_as_cents = rat.cents(ratios[i]) | |||
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 | if i < #ratios then | ||
text = text .. delimiter | text = text .. delimiter |