local p = {}
-- greatest common divisor of a and b
-- source: https://rosettacode.org/wiki/Greatest_common_divisor#Lua
local function gcd(a, b)
while b ~= 0 do
a, b = b, a % b
end
return math.abs(a)
end
local function calc_step_span(edo, val)
local multiplicity = gcd(edo, val)
if multiplicity == 1 then
test = 0
for i=0, edo-1 do
if test == 1 then
return i, 1
end
test = test + val
test = test % edo
end
end
return nil, multiplicity
end
-- round number to decimals. halfway up for positive numbers
local function round(number, decimals)
local scale = 10^(decimals or 0)
local nt = math.floor(number * scale + 0.5)
return nt / scale
end
local function log2(v)
return math.log(v) / math.log(2)
end
local function fmt_sign(s)
if type(s) == 'number' then
s = tostring(s)
end
return s:gsub('-', '−')
end
--[[
primes: list of primes
prec: number of decimals (for abs errors)
fse_arg: should fifthspans be included as well
]]
local function edoprox(edo, primes, title, prec, fse_arg)
local f = 1/edo
local tpri = {'! colspan="2" | Prime number'}
local tabs = {'! rowspan="2" | Error\n! absolute ([[cent|¢]])'}
local trel = {'! [[Relative error|relative]] (%)'}
local tdeg = {'! rowspan="2" | Mapping\n! [[patent val]] \'\'v\'\''}
local tdegor = {"! ''v'' ([[octave-reduced|mod "..edo..']])'}
local fspans = {'! colspan="2" | [[Fifthspan]]'}
local fmt_abs = string.format(' %%+.%df', prec)
local fmt_rel = ' %+.0f'
local fs_enable = fse_arg or false
local function cat_row(tab, row)
tab = tab + row
end
for _, p in pairs(primes) do
s = log2(p)
v = s*edo
ev = round(v)
evo = ev % edo
table.insert(tpri, ' ' .. p)
table.insert(tabs, ''..fmt_sign(string.format(fmt_abs, 1200 * (ev - v ) / edo)))
table.insert(trel, ''..fmt_sign(string.format(fmt_rel, 100 * (ev - v))))
table.insert(tdeg, ' ' .. ev)
table.insert(tdegor, ' ' .. evo)
end
if fs_enable then
local vf = round(log2(3)*edo) % edo
local sss, multi = calc_step_span(edo, vf)
if sss ~= nil then
for _, p in pairs(primes) do
s = log2(p)
v = s*edo
v = round(v) % edo
local sp = tonumber(v)*sss % edo
local rsp = sp
if (rsp > edo/2) then
rsp = rsp - edo
end
if rsp ~= 0 then
rsp = string.format('%+.0f', rsp)
end
table.insert(fspans, ' '.. fmt_sign(rsp))
end
else
local reason = ''
if (multi > 1) then
reason = reason .. ' ('.. multi ..'x [[' .. (edo / multi) .. '-EDO]])'
end
table.insert(fspans, ' N/A' .. reason)
end
end
local border_style = ''
if fs_enable then
border_style = ' class="thick-border" style="border-top: 2px solid #aaa"'
end
local titleMarkup = ''
if title then
titleMarkup = '|-\n|+ ' .. title .. '\n'
end
local data_rows = ''
data_rows = data_rows .. '|-'..border_style..'\n' .. table.concat(tabs, '\n|') .. '\n'
data_rows = data_rows .. '|-\n' .. table.concat(trel, '\n|') .. '\n'
data_rows = data_rows .. '|-'..border_style..'\n' .. table.concat(tdeg, '\n|') .. '\n'
data_rows = data_rows .. '|-\n' .. table.concat(tdegor, '\n|') .. '\n'
if fs_enable then
data_rows = data_rows .. '|-'..border_style..'\n' .. table.concat(fspans, '\n|') .. '\n'
end
return '{| class="wikitable center-all"\n' .. titleMarkup ..
'|-\n' ..
table.concat(tpri, '\n!') .. '\n' .. data_rows .. '|}'
end
local primes = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
}
-- evaluate input on error use default
local function eval_num_arg(input, def_value)
local result = input
if type(input) ~= 'number' then
result = def_value
if type(input) == 'string' then
input = input:match("^%s*(.-)%s*$")
if string.len(input) > 0 then
result = tonumber(input)
end
end
end
return result
end
-- calculate default precicion by EDO magnitude
local function prec_by_edo(edo)
return math.floor(math.log(edo*1.9)/math.log(10))
end
function p.primes_in_edo (frame)
-- optional EDO number, default: 12
local edo = eval_num_arg(frame.args['edo'], 12)
-- optional number of columns, default: 8
local columns = eval_num_arg(frame.args['columns'], 8)
-- optional start column, default: start with prime 2
local start = eval_num_arg(frame.args['start'], 1)
local title = frame.args['title'] or 'Approximation of prime intervals in ' .. edo .. ' EDO'
-- optional precision for abs error, default about 3 digits
local prec = eval_num_arg(frame.args['prec'], prec_by_edo(edo))
-- option to show fifthspan information, default: false
local fs = (frame.args['fs'] == 1) or (frame.args['fs'] == '1') or false
return edoprox( edo, {unpack(primes, start, start+columns-1)}, title, prec, fs)
end
return p;