Module:Chord edo approximation: Difference between revisions
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
-- Chord EDO Approximations Module | -- Chord EDO Approximations Module | ||
-- Calculates EDO approximations for JI chords like 4:5:6 or 4:5:6:7 | -- Calculates EDO approximations for JI chords like 4:5:6 or 4:5:6:7 | ||
-- Metric: RMS of per-note errors around the optimal reference (least-squares). | -- Metric: RMS of per-note errors around the optimal reference (least-squares), | ||
-- Usage: {{#invoke:Chord_EDO_Approximation|main|chord=4:5:6|max_rms= | -- expressed as % of EDO step (relative) so that smaller EDOs aren't penalized | ||
-- for their inherently coarser resolution. | |||
-- Usage: {{#invoke:Chord_EDO_Approximation|main|chord=4:5:6|max_rms=15|min_edo=5|max_edo=60}} | |||
local u = require("Module:Utils") | local u = require("Module:Utils") | ||
local yesno = require("Module:Yesno") | local yesno = require("Module:Yesno") | ||
| Line 8: | Line 10: | ||
-- ===== CONFIGURATION VARIABLES ===== | -- ===== CONFIGURATION VARIABLES ===== | ||
local DEFAULT_MAX_RMS = | local DEFAULT_MAX_RMS = 15 -- Max RMS error in % of EDO step | ||
local DEFAULT_MIN_EDO = 5 | local DEFAULT_MIN_EDO = 5 | ||
local DEFAULT_MAX_EDO = 60 | local DEFAULT_MAX_EDO = 60 | ||
| Line 45: | Line 47: | ||
end | end | ||
-- | -- Computes both absolute (cents) and relative (% of step) RMS around the | ||
-- optimal-reference offset (mean of per-note errors). | |||
-- optimal-reference | |||
local function calculate_chord_approximation(interval_cents_list, edo) | local function calculate_chord_approximation(interval_cents_list, edo) | ||
local edostep = 1200 / edo | local edostep = 1200 / edo | ||
| Line 63: | Line 64: | ||
end | end | ||
-- Optimal reference offset: mean of note errors (least-squares | -- Optimal-reference offset: mean of note errors (least-squares centering) | ||
local n = #note_errors | local n = #note_errors | ||
local sum = 0 | local sum = 0 | ||
| Line 69: | Line 70: | ||
local mean = sum / n | local mean = sum / n | ||
-- RMS of centered errors | -- RMS of centered errors, in cents and as % of EDO step | ||
local sq_sum = 0 | local sq_sum = 0 | ||
for _, e in ipairs(note_errors) do | for _, e in ipairs(note_errors) do | ||
| Line 75: | Line 76: | ||
sq_sum = sq_sum + d * d | sq_sum = sq_sum + d * d | ||
end | end | ||
local | local rms_cents = math.sqrt(sq_sum / n) | ||
local rms_relative = (rms_cents / edostep) * 100 | |||
return { | return { | ||
steps = steps, | steps = steps, | ||
abs_errors = abs_errors, | abs_errors = abs_errors, | ||
mean_offset = mean, | mean_offset = mean, | ||
rms_cents = rms_cents, | |||
rms_relative = rms_relative, | |||
} | } | ||
end | end | ||
| Line 125: | Line 127: | ||
for edo = min_edo, max_edo do | for edo = min_edo, max_edo do | ||
local data = calculate_chord_approximation(intervals_cents, edo) | local data = calculate_chord_approximation(intervals_cents, edo) | ||
if data. | if data.rms_relative <= max_rms then | ||
data.edo = edo | data.edo = edo | ||
table.insert(results, data) | table.insert(results, data) | ||
| Line 132: | Line 134: | ||
if #results == 0 then | if #results == 0 then | ||
return "No edos found within RMS | return "No edos found within RMS tolerance of " .. max_rms .. "%" | ||
end | end | ||
| Line 159: | Line 161: | ||
table.insert(output, '|+ style="font-size: 105%;" | ' .. caption_main | table.insert(output, '|+ style="font-size: 105%;" | ' .. caption_main | ||
.. string.format('<br /><span style="font-size: 0.75em;">\'\'intervals: %s · ≤ %dedo, RMS error ≤ %g | .. string.format('<br /><span style="font-size: 0.75em;">\'\'intervals: %s · ≤ %dedo, RMS rel. error ≤ %g%%\'\'</span>', | ||
intervals_display, max_edo, max_rms)) | intervals_display, max_edo, max_rms)) | ||
| Line 168: | Line 170: | ||
.. ' !! class="unsortable" | Cents ([[cent|¢]])' | .. ' !! class="unsortable" | Cents ([[cent|¢]])' | ||
.. ' !! class="unsortable" | Absolute errors ([[cent|¢]])' | .. ' !! class="unsortable" | Absolute errors ([[cent|¢]])' | ||
.. ' !! RMS | .. ' !! RMS ([[cent|¢]])' | ||
.. ' !! RMS ([[relative cent|%]])') | |||
for _, r in ipairs(results) do | for _, r in ipairs(results) do | ||
| Line 193: | Line 196: | ||
local err_str = table.concat(err_parts, " ") | local err_str = table.concat(err_parts, " ") | ||
local | local rms_c = string.format("%.2f", r.rms_cents) | ||
local rms_r = string.format("%.2f", r.rms_relative) | |||
local play_btn = string.format( | local play_btn = string.format( | ||
| Line 200: | Line 204: | ||
table.insert(output, '|-') | table.insert(output, '|-') | ||
table.insert(output, string.format('| %s || %s || %s || %s || %s || %s', | table.insert(output, string.format('| %s || %s || %s || %s || %s || %s || %s', | ||
play_btn, edo_link, steps_str, cents_str, err_str, | play_btn, edo_link, steps_str, cents_str, err_str, rms_c, rms_r)) | ||
end | end | ||