Module:Interval edo approximation: Difference between revisions

Pailiaq (talk | contribs)
No edit summary
Pailiaq (talk | contribs)
No edit summary
 
(18 intermediate revisions by 5 users not shown)
Line 2: Line 2:
-- Calculates EDO approximations for just intervals
-- Calculates EDO approximations for just intervals
-- Usage: {{#invoke:EDO_Approximations|main|interval=3/2|tolerance=9|min_edo=5|max_edo=60}}
-- Usage: {{#invoke:EDO_Approximations|main|interval=3/2|tolerance=9|min_edo=5|max_edo=60}}
 
local u = require("Module:Utils")
local yesno = require("Module:Yesno")
local p = {}
local p = {}


-- ===== CONFIGURATION VARIABLES =====
-- ===== CONFIGURATION VARIABLES =====
local DEFAULT_TOLERANCE = 9.0  -- Relative error tolerance in percent
local DEFAULT_TOLERANCE = 13.0  -- Relative error tolerance in percent
local DEFAULT_MIN_EDO = 5      -- Minimum EDO to check
local DEFAULT_MIN_EDO = 5      -- Minimum EDO to check
local DEFAULT_MAX_EDO = 60    -- Maximum EDO to check
local DEFAULT_MAX_EDO = 60    -- Maximum EDO to check
Line 14: Line 15:
-- Python: return 1200 * math.log2(ratio)
-- Python: return 1200 * math.log2(ratio)
local function cents(ratio)
local function cents(ratio)
    -- Lua doesn't have log2, so use change of base formula
     return 1200 * u.log2(ratio)
     return 1200 * (math.log(ratio) / math.log(2))
end
 
-- Parse a ratio string like '3/2' into a number
-- Python: parts = ratio_str.strip().split('/'); return float(parts[0]) / float(parts[1])
local function parse_ratio(ratio_str)
    -- Strip whitespace (equivalent to Python's strip())
    ratio_str = ratio_str:match("^%s*(.-)%s*$")
 
    -- Split on '/' and parse numerator/denominator
    local slash_pos = ratio_str:find("/")
    if not slash_pos then
        return nil
    end
 
    local num_str = ratio_str:sub(1, slash_pos - 1)
    local denom_str = ratio_str:sub(slash_pos + 1)
 
    local num = tonumber(num_str)
    local denom = tonumber(denom_str)
 
    if not num or not denom or denom == 0 then
        return nil
    end
 
    -- Return exact division (equivalent to Python's float division)
    return num / denom
end
end


Line 117: Line 91:
     local args = frame.args
     local args = frame.args
     local interval_str = args.interval or args[1]
     local interval_str = args.interval or args[1]
 
local interval_name = args.interval_name  -- Optional display name
     -- Convert string parameters to numbers using config defaults
     -- Convert string parameters to numbers using config defaults
     local tolerance = tonumber(args.tolerance) or DEFAULT_TOLERANCE
     local tolerance = tonumber(args.tolerance) or DEFAULT_TOLERANCE
Line 129: Line 103:


     -- Parse interval string to numeric ratio
     -- Parse interval string to numeric ratio
     local ratio = parse_ratio(interval_str)
     local ratio = u.eval_num_arg(interval_str)
     if not ratio then
     if not ratio then
         return "Error: Invalid interval format (use format like '3/2')"
         return "Error: Invalid interval format (use format like '3/2')"
Line 138: Line 112:


     if #results == 0 then
     if #results == 0 then
         return "No EDOs found within tolerance of " .. tolerance .. "%"
         return "No edos found within tolerance of " .. tolerance .. "%"
     end
     end


     -- Build the wikitable
     -- Build the wikitable
     -- mw-collapsible: adds [hide] toggle button
     -- mw-collapsible: adds [hide] toggle button
    -- mw-collapsed: table defaults to collapsed ##removed
     -- sortable: makes columns sortable by clicking headers
     -- sortable: makes columns sortable by clicking headers
     local output = {}
     local output = {}
     table.insert(output, '{| class="wikitable mw-collapsible sortable"')
     table.insert(output, '{| class="wikitable center-all mw-collapsible sortable"')


     -- Calculate the precise ratio in cents for the caption
     -- Calculate the precise ratio in cents for the caption
     local ratio_cents = cents(ratio)
     local ratio_cents = cents(ratio)
     table.insert(output, string.format('|+ EDO Approximations for %s (%.2f¢)', interval_str, ratio_cents))
     -- Include subtitle info in caption to avoid breaking sortable functionality
local display_name = (interval_name and interval_name ~= "") and interval_name or interval_str
 
table.insert(output, '|+ style="font-size: 105%;" | '
    .. string.format('Edo&nbsp;approximations&nbsp;for&nbsp;%s&nbsp;(%.2f{{c}})<br /><span style="font-size: 0.75em;">\'\'&le;&nbsp;%dedo,&nbsp;relative&nbsp;error&nbsp;&le;&nbsp;%g%%\'\'</span>',
        display_name, ratio_cents, max_edo, tolerance))
     table.insert(output, '|-')
     table.insert(output, '|-')
    -- Use class="sortbottom" to exclude subtitle from sorting logic
     table.insert(output, '! Edo'
     table.insert(output, string.format('| colspan="5" class="sortbottom" style="text-align:center; font-size:90%%;" | \'\'Up to %dedo, Tolerance: ≤%g%%%%\'\'', max_edo, tolerance))
    .. ' !! class="unsortable" | Step size'
    table.insert(output, '|-')
    .. ' !! Cents ([[cent|¢]])'
    table.insert(output, '! EDO !! Step size !! Approximation ([[Cent|¢]]) !! Absolute Error ([[Cent|¢]]) !! [[Relative_interval_error|Relative Error]] ([[Relative_cent|%]])')
    .. ' !! Absolute error ([[cent|¢]])'
    .. ' !! [[Relative interval error|Relative error]] ([[relative cent|%]])')


     for _, result in ipairs(results) do
     for _, result in ipairs(results) do
Line 179: Line 160:
     table.insert(output, '|}')
     table.insert(output, '|}')


     return table.concat(output, '\n')
     local result = table.concat(output, '\n')
    if yesno(frame.args["debug"]) == true then
    result = '<syntaxhighlight lang="wikitext">' .. result .. '</syntaxhighlight>'
    end
   
    return frame:preprocess(result)
end
end


return p
return p