<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://xenreference.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AInterval_edo_approximation</id>
	<title>Module:Interval edo approximation - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://xenreference.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3AInterval_edo_approximation"/>
	<link rel="alternate" type="text/html" href="https://xenreference.com/wiki/index.php?title=Module:Interval_edo_approximation&amp;action=history"/>
	<updated>2026-06-11T10:47:14Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.44.2</generator>
	<entry>
		<id>https://xenreference.com/wiki/index.php?title=Module:Interval_edo_approximation&amp;diff=7340&amp;oldid=prev</id>
		<title>Inthar: Created page with &quot;-- EDO Approximations Module -- Calculates EDO approximations for just intervals -- Usage: {{#invoke:EDO_Approximations|main|interval=3/2|tolerance=9|min_edo=5|max_edo=60}} local u = require(&quot;Module:Utils&quot;) local yesno = require(&quot;Module:Yesno&quot;) local p = {}  -- ===== CONFIGURATION VARIABLES ===== local DEFAULT_TOLERANCE = 13.0  -- Relative error tolerance in percent local DEFAULT_MIN_EDO = 5      -- Minimum EDO to check local DEFAULT_MAX_EDO = 60     -- Maximum EDO to ch...&quot;</title>
		<link rel="alternate" type="text/html" href="https://xenreference.com/wiki/index.php?title=Module:Interval_edo_approximation&amp;diff=7340&amp;oldid=prev"/>
		<updated>2026-05-26T20:52:41Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;-- EDO Approximations Module -- Calculates EDO approximations for just intervals -- Usage: {{#invoke:EDO_Approximations|main|interval=3/2|tolerance=9|min_edo=5|max_edo=60}} local u = require(&amp;quot;Module:Utils&amp;quot;) local yesno = require(&amp;quot;Module:Yesno&amp;quot;) local p = {}  -- ===== CONFIGURATION VARIABLES ===== local DEFAULT_TOLERANCE = 13.0  -- Relative error tolerance in percent local DEFAULT_MIN_EDO = 5      -- Minimum EDO to check local DEFAULT_MAX_EDO = 60     -- Maximum EDO to ch...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;-- EDO Approximations Module&lt;br /&gt;
-- Calculates EDO approximations for just intervals&lt;br /&gt;
-- Usage: {{#invoke:EDO_Approximations|main|interval=3/2|tolerance=9|min_edo=5|max_edo=60}}&lt;br /&gt;
local u = require(&amp;quot;Module:Utils&amp;quot;)&lt;br /&gt;
local yesno = require(&amp;quot;Module:Yesno&amp;quot;)&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
-- ===== CONFIGURATION VARIABLES =====&lt;br /&gt;
local DEFAULT_TOLERANCE = 13.0  -- Relative error tolerance in percent&lt;br /&gt;
local DEFAULT_MIN_EDO = 5      -- Minimum EDO to check&lt;br /&gt;
local DEFAULT_MAX_EDO = 60     -- Maximum EDO to check&lt;br /&gt;
-- ====================================&lt;br /&gt;
&lt;br /&gt;
-- Convert a frequency ratio to cents&lt;br /&gt;
-- Python: return 1200 * math.log2(ratio)&lt;br /&gt;
local function cents(ratio)&lt;br /&gt;
    return 1200 * u.log2(ratio)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Python-compatible rounding function&lt;br /&gt;
-- Python&amp;#039;s round() uses &amp;quot;round half to even&amp;quot; (banker&amp;#039;s rounding)&lt;br /&gt;
local function round(x)&lt;br /&gt;
    local floor_x = math.floor(x)&lt;br /&gt;
    local frac = x - floor_x&lt;br /&gt;
&lt;br /&gt;
    if frac &amp;lt; 0.5 then&lt;br /&gt;
        return floor_x&lt;br /&gt;
    elseif frac &amp;gt; 0.5 then&lt;br /&gt;
        return floor_x + 1&lt;br /&gt;
    else&lt;br /&gt;
        -- Exactly 0.5: round to nearest even number (banker&amp;#039;s rounding)&lt;br /&gt;
        if floor_x % 2 == 0 then&lt;br /&gt;
            return floor_x&lt;br /&gt;
        else&lt;br /&gt;
            return floor_x + 1&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Find the best approximation of an interval in a given EDO&lt;br /&gt;
-- Python: best_step = round(ratio_cents / edostep)&lt;br /&gt;
local function find_best_approximation(ratio_cents, edo)&lt;br /&gt;
    local edostep = 1200 / edo&lt;br /&gt;
    -- Find the nearest step using Python-compatible rounding&lt;br /&gt;
    local best_step = round(ratio_cents / edostep)&lt;br /&gt;
    local approximation_cents = best_step * edostep&lt;br /&gt;
    local absolute_error = approximation_cents - ratio_cents&lt;br /&gt;
    -- Python: relative_error = (absolute_error / edostep) * 100 if edostep != 0 else 0&lt;br /&gt;
    local relative_error = (absolute_error / edostep) * 100&lt;br /&gt;
&lt;br /&gt;
    return best_step, absolute_error, relative_error&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculate all EDO approximations within tolerance for a given ratio&lt;br /&gt;
-- Python: for edo in range(EDO_RANGE_MIN, EDO_RANGE_MAX + 1):&lt;br /&gt;
--           if abs(rel_error) &amp;lt;= RELATIVE_ERROR_TOLERANCE:&lt;br /&gt;
local function calculate_edo_approximations(ratio, tolerance, min_edo, max_edo)&lt;br /&gt;
    local ratio_cents = cents(ratio)&lt;br /&gt;
    local results = {}&lt;br /&gt;
&lt;br /&gt;
    -- Loop through EDO range (inclusive on both ends, like Python&amp;#039;s range)&lt;br /&gt;
    for edo = min_edo, max_edo do&lt;br /&gt;
        local steps, abs_error, rel_error = find_best_approximation(ratio_cents, edo)&lt;br /&gt;
&lt;br /&gt;
        -- Filter by tolerance using absolute value&lt;br /&gt;
        if math.abs(rel_error) &amp;lt;= tolerance then&lt;br /&gt;
            table.insert(results, {&lt;br /&gt;
                edo = edo,&lt;br /&gt;
                steps = steps,&lt;br /&gt;
                abs_error = abs_error,&lt;br /&gt;
                rel_error = rel_error&lt;br /&gt;
            })&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return results&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Format a number with sign and 2 decimal places&lt;br /&gt;
local function format_error(value)&lt;br /&gt;
    if value &amp;gt;= 0 then&lt;br /&gt;
        return string.format(&amp;quot;+%.2f&amp;quot;, value)&lt;br /&gt;
    else&lt;br /&gt;
        return string.format(&amp;quot;%.2f&amp;quot;, value)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Main function to generate the wikitable&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
    -- Get parameters from template invocation&lt;br /&gt;
    local args = frame.args&lt;br /&gt;
    local interval_str = args.interval or args[1]&lt;br /&gt;
	local interval_name = args.interval_name   -- Optional display name&lt;br /&gt;
    -- Convert string parameters to numbers using config defaults&lt;br /&gt;
    local tolerance = tonumber(args.tolerance) or DEFAULT_TOLERANCE&lt;br /&gt;
    local min_edo = tonumber(args.min_edo) or DEFAULT_MIN_EDO&lt;br /&gt;
    local max_edo = tonumber(args.max_edo) or DEFAULT_MAX_EDO&lt;br /&gt;
&lt;br /&gt;
    -- Validate interval parameter (required, no default)&lt;br /&gt;
    if not interval_str then&lt;br /&gt;
        return &amp;quot;Error: No interval specified&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Parse interval string to numeric ratio&lt;br /&gt;
    local ratio = u.eval_num_arg(interval_str)&lt;br /&gt;
    if not ratio then&lt;br /&gt;
        return &amp;quot;Error: Invalid interval format (use format like &amp;#039;3/2&amp;#039;)&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Calculate approximations (ratio is a number, not string)&lt;br /&gt;
    local results = calculate_edo_approximations(ratio, tolerance, min_edo, max_edo)&lt;br /&gt;
&lt;br /&gt;
    if #results == 0 then&lt;br /&gt;
        return &amp;quot;No edos found within tolerance of &amp;quot; .. tolerance .. &amp;quot;%&amp;quot;&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    -- Build the wikitable&lt;br /&gt;
    -- mw-collapsible: adds [hide] toggle button&lt;br /&gt;
    -- mw-collapsed: table defaults to collapsed ##removed&lt;br /&gt;
    -- sortable: makes columns sortable by clicking headers&lt;br /&gt;
    local output = {}&lt;br /&gt;
    table.insert(output, &amp;#039;{| class=&amp;quot;wikitable center-all mw-collapsible sortable&amp;quot;&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
    -- Calculate the precise ratio in cents for the caption&lt;br /&gt;
    local ratio_cents = cents(ratio)&lt;br /&gt;
    -- Include subtitle info in caption to avoid breaking sortable functionality&lt;br /&gt;
	local display_name = (interval_name and interval_name ~= &amp;quot;&amp;quot;) and interval_name or interval_str&lt;br /&gt;
&lt;br /&gt;
	table.insert(output, &amp;#039;|+ style=&amp;quot;font-size: 105%;&amp;quot; | &amp;#039;&lt;br /&gt;
    .. string.format(&amp;#039;Edo&amp;amp;nbsp;approximations&amp;amp;nbsp;for&amp;amp;nbsp;%s&amp;amp;nbsp;(%.2f{{c}})&amp;lt;br /&amp;gt;&amp;lt;span style=&amp;quot;font-size: 0.75em;&amp;quot;&amp;gt;\&amp;#039;\&amp;#039;&amp;amp;le;&amp;amp;nbsp;%dedo,&amp;amp;nbsp;relative&amp;amp;nbsp;error&amp;amp;nbsp;&amp;amp;le;&amp;amp;nbsp;%g%%\&amp;#039;\&amp;#039;&amp;lt;/span&amp;gt;&amp;#039;,&lt;br /&gt;
        display_name, ratio_cents, max_edo, tolerance))&lt;br /&gt;
    table.insert(output, &amp;#039;|-&amp;#039;)&lt;br /&gt;
    table.insert(output, &amp;#039;! Edo&amp;#039;&lt;br /&gt;
    					.. &amp;#039; !! class=&amp;quot;unsortable&amp;quot; | Step size&amp;#039;&lt;br /&gt;
    					.. &amp;#039; !! Cents ([[cent|¢]])&amp;#039;&lt;br /&gt;
    					.. &amp;#039; !! Absolute error ([[cent|¢]])&amp;#039;&lt;br /&gt;
    					.. &amp;#039; !! [[Relative interval error|Relative error]] ([[relative cent|%]])&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
    for _, result in ipairs(results) do&lt;br /&gt;
        -- Python: edo_link = f&amp;quot;[[{edo}edo|{edo}]]&amp;quot;&lt;br /&gt;
        local edo_link = string.format(&amp;quot;[[%dedo|%d]]&amp;quot;, result.edo, result.edo)&lt;br /&gt;
&lt;br /&gt;
        -- Python: step_str = f&amp;quot;{steps}\\{edo}&amp;quot;&lt;br /&gt;
        local step_size = string.format(&amp;quot;%d\\%d&amp;quot;, result.steps, result.edo)&lt;br /&gt;
&lt;br /&gt;
        -- Calculate approximation in cents: steps * (1200/edo)&lt;br /&gt;
        local approximation_cents = result.steps * (1200 / result.edo)&lt;br /&gt;
        local approx_str = string.format(&amp;quot;%.2f&amp;quot;, approximation_cents)&lt;br /&gt;
&lt;br /&gt;
        -- Python: abs_err = f&amp;quot;{result[&amp;#039;abs_error&amp;#039;]:+.2f}&amp;quot;&lt;br /&gt;
        local abs_err = format_error(result.abs_error)&lt;br /&gt;
&lt;br /&gt;
        -- Python: rel_err = f&amp;quot;{result[&amp;#039;rel_error&amp;#039;]:+.2f}&amp;quot;&lt;br /&gt;
        local rel_err = format_error(result.rel_error)&lt;br /&gt;
&lt;br /&gt;
        table.insert(output, &amp;#039;|-&amp;#039;)&lt;br /&gt;
        table.insert(output, string.format(&amp;#039;| %s || %s || %s || %s || %s&amp;#039;, edo_link, step_size, approx_str, abs_err, rel_err))&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    table.insert(output, &amp;#039;|}&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
    local result = table.concat(output, &amp;#039;\n&amp;#039;)&lt;br /&gt;
    if yesno(frame.args[&amp;quot;debug&amp;quot;]) == true then&lt;br /&gt;
    	result = &amp;#039;&amp;lt;syntaxhighlight lang=&amp;quot;wikitext&amp;quot;&amp;gt;&amp;#039; .. result .. &amp;#039;&amp;lt;/syntaxhighlight&amp;gt;&amp;#039;&lt;br /&gt;
    end&lt;br /&gt;
    &lt;br /&gt;
    return frame:preprocess(result)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Inthar</name></author>
	</entry>
</feed>