Module:Harmonics in equal: Difference between revisions
Jump to navigation
Jump to search
Default number of columns set to 11. Cut off is now at 31 for prime, 23 for odd, and 12 for integer |
+ link to Error page |
||
| (48 intermediate revisions by 5 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local utils = require("Module:Utils") | |||
local yesno = require("Module:yesno") | |||
-- direct mapping | -- direct mapping | ||
local function map(p, steps, num, denom) | local function map(p, steps, num, denom) | ||
local s = math.log(p) / math.log(num/denom) | local s = math.log(p) / math.log(num/denom) | ||
return math.floor(s*steps + .5) | return math.floor(s * steps + 0.5) | ||
end | end | ||
-- check consistency for 9, 15 and 21 | -- check consistency for 9, 15 and 21 | ||
| Line 15: | Line 17: | ||
local p21 = map(21, steps, num, denom) | local p21 = map(21, steps, num, denom) | ||
return (p9 == 2*p3) and (p15 == p3+p5) and (p21 == p3+p7) | return (p9 == 2 * p3) and (p15 == p3 + p5) and (p21 == p3 + p7) | ||
end | end | ||
local function approx(steps, num, denom, intervals, title, prec, reduction) | local function approx(steps, num, denom, intervals, title, prec, reduction, collapsed) | ||
local tpri = { | local thead | ||
local tabs = { | if collapsed then | ||
local trel = { | thead = "{| class=\"wikitable center-all mw-collapsible mw-collapsed\"\n" | ||
local tdeg | else | ||
thead = "{| class=\"wikitable center-all\"\n" | |||
end | |||
local tpri = {"! colspan=\"2\" | Harmonic "} | |||
local tabs = {"! rowspan=\"2\" | Error \n! [[Error|Absolute]] ([[cent|¢]]) "} | |||
local trel = {"! [[Relative interval error|Relative]] ([[relative cent|%]]) "} | |||
local tdeg | |||
if reduction then | if reduction then | ||
tdeg = { | tdeg = {"! colspan=\"2\" | Steps<br />([[octave reduction|reduced]])"} | ||
else | |||
tdeg = {"! colspan=\"2\" | Step"} | |||
end | end | ||
local fmt_abs = string.format( | local fmt_abs = string.format("%%+.%df", prec) | ||
local fmt_rel = | local fmt_rel = "%+.1f" | ||
local equave = math.log(num/denom) / math.log(2) | local equave = math.log(num/denom) / math.log(2) | ||
for _, p in pairs(intervals) do | for _, p in pairs(intervals) do | ||
s = math.log(p) / math.log(num/denom) | s = math.log(p) / math.log(num/denom) | ||
v = s*steps | v = s * steps | ||
ev = math.floor(v + .5) | ev = math.floor(v + 0.5) | ||
table.insert(tpri, | table.insert(tpri, "" .. p) | ||
table.insert(tabs, string.format(fmt_abs, 1200 * equave * (ev - v ) / steps)) | table.insert(tabs, "" .. string.gsub(string.format(fmt_abs, 1200 * equave * (ev - v ) / steps), "%-", "-")) | ||
table.insert(trel, string.format(fmt_rel, 100 * (ev - v))) | table.insert(trel, "" .. string.gsub(string.format(fmt_rel, 100 * (ev - v)), "%-", "-")) | ||
if reduction then | if reduction then | ||
table.insert(tdeg, | table.insert(tdeg, "" .. ev .. "<br />(".. ev % steps .. ")") | ||
else | else | ||
table.insert(tdeg, | table.insert(tdeg, "" .. ev) | ||
end | end | ||
end | end | ||
local titleMarkup = | local titleMarkup = "" | ||
if title then | if title then | ||
titleMarkup = | titleMarkup = "|+ style=\"font-size: 105%; white-space: nowrap;\" | " .. title .. "\n" | ||
end | end | ||
thead = thead .. | |||
titleMarkup .. | titleMarkup .. | ||
"|-\n" .. | |||
table.concat(tpri, | table.concat(tpri, "\n! ") .. "\n" .. | ||
"|-\n" .. | |||
table.concat(tabs, | table.concat(tabs, "\n| ") .. "\n" .. | ||
"|-\n" .. | |||
table.concat(trel, | table.concat(trel, "\n| ") .. "\n" .. | ||
"|-\n" .. | |||
table.concat(tdeg, "\n| ") .. "\n" .. | |||
"|}" | |||
return thead | |||
end | end | ||
local intervals = {} | local intervals = {} | ||
intervals.prime = {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 } | intervals.prime = {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, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251 } | ||
intervals.odd = {3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53 } | intervals.odd = {3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127 } | ||
intervals.integer = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, | intervals.integer = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128 } | ||
-- evaluate input on error use default | -- evaluate input on error use default | ||
local function eval_num_arg(input, def_value) | local function eval_num_arg(input, def_value) | ||
local result = input | local result = input | ||
if type(input) ~= | if type(input) ~= "number" then | ||
result = def_value | result = def_value | ||
if type(input) == | if type(input) == "string" then | ||
input = input:match("^%s*(.-)%s*$") | input = input:match("^%s*(.-)%s*$") | ||
if string.len(input) > 0 then | if string.len(input) > 0 then | ||
| Line 83: | Line 95: | ||
-- calculate default precision | -- calculate default precision | ||
local function prec_by_equal(steps, num, denom) | local function prec_by_equal(steps, num, denom) | ||
return math.floor(math.log(steps*1.9*math.log(2)/math.log(num/denom))/math.log(10)) | return math.floor(10 * math.log(steps * 1.9 * math.log(2) / math.log(num / denom)) / math.log(10)) / 10 | ||
end | end | ||
function p.harmonics_in_equal (frame) | function p.harmonics_in_equal (frame) | ||
-- optional number of steps, default: 12 | -- optional number of steps, default: 12 | ||
local steps = eval_num_arg(frame.args[ | local steps = eval_num_arg(frame.args["steps"], 12) | ||
-- numerator, default: 2 | -- numerator, default: 2 | ||
local num = eval_num_arg(frame.args[ | local num = eval_num_arg(frame.args["num"], 2) | ||
-- denominator, default: 1 | -- denominator, default: 1 | ||
local denom = eval_num_arg(frame.args[ | local denom = eval_num_arg(frame.args["denom"], 1) | ||
-- optional number of columns, default: 11 | -- optional number of columns, default: 11 | ||
local columns = eval_num_arg(frame.args[ | local columns = eval_num_arg(frame.args["columns"], 11) | ||
-- optional start column, default: start with prime 2 | -- optional start column, default: start with prime 2 | ||
local start = eval_num_arg(frame.args[ | local start = eval_num_arg(frame.args["start"], 1) | ||
-- option intervals | -- option intervals | ||
local select_intervals = "integer" | local select_intervals = "integer" | ||
local name = steps .. | local name = steps .. "ed" .. num .. "/" .. denom | ||
if denom == 1 then | if denom == 1 then | ||
if num == 2 then | if num == 2 then | ||
name = steps .. | name = steps .. "edo" | ||
select_intervals = "odd" | select_intervals = "odd" | ||
if check_consistency(steps, num, denom) then | if check_consistency(steps, num, denom) then | ||
| Line 111: | Line 122: | ||
end | end | ||
elseif num == 3 then | elseif num == 3 then | ||
name = steps .. | name = steps .. "edt" | ||
else | else | ||
name = steps .. | name = steps .. "ed" .. num | ||
end | end | ||
end | end | ||
if num == 3 and denom == 2 then | if num == 3 and denom == 2 then | ||
name = steps .. | name = steps .. "edf" | ||
end | end | ||
-- override default intervals | -- override default intervals | ||
if frame.args[ | if frame.args["intervals"] and string.len(frame.args["intervals"]) > 0 then | ||
select_intervals = frame.args[ | select_intervals = frame.args["intervals"] | ||
end | end | ||
if select_intervals == "odd" then | if select_intervals == "odd" then | ||
title_intervals = "odd harmonics" | title_intervals = "odd harmonics" | ||
elseif select_intervals == "integer" then | elseif select_intervals == "integer" then | ||
title_intervals = "harmonics" | title_intervals = "harmonics" | ||
else | |||
title_intervals = "prime harmonics" | |||
end | end | ||
local title = frame.args[ | local title = frame.args["title"] | ||
if title == nil or string.len(title) == 0 then | if title == nil or string.len(title) == 0 then | ||
title = "Approximation of ".. title_intervals .. " in " .. name | title = "Approximation of ".. title_intervals .. " in " .. name | ||
end | end | ||
-- optional precision for abs error, default about 3 digits | -- optional precision for abs error, default about 3 digits | ||
local prec = eval_num_arg(frame.args[ | local prec = eval_num_arg(frame.args["prec"], prec_by_equal(steps, num, denom)) | ||
local reduction = true | local reduction = true | ||
if steps == 1 then | if steps == 1 then | ||
reduction = false | reduction = false | ||
end | end | ||
local collapsed = utils.value_provided(frame.args["collapsed"]) | |||
local debugg = yesno(frame.args["debug"]) | |||
local result = approx(steps, num, denom, {unpack(intervals[select_intervals], start, start + columns - 1)}, title, prec, reduction, collapsed) | |||
if debugg == true then | |||
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>" | |||
end | |||
return frame:preprocess(result) | |||
end | end | ||
return p | return p | ||
Latest revision as of 10:37, 31 March 2026
- This module should not be invoked directly; use its corresponding template instead: Template:Harmonics in equal.
Calculates approximations for harmonics in equal-step tunings and present them in form of a table.
| Introspection summary for Module:Harmonics in equal | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||||||||
No function descriptions were provided. The Lua code may have further information.
local p = {}
local utils = require("Module:Utils")
local yesno = require("Module:yesno")
-- direct mapping
local function map(p, steps, num, denom)
local s = math.log(p) / math.log(num/denom)
return math.floor(s * steps + 0.5)
end
-- check consistency for 9, 15 and 21
local function check_consistency(steps, num, denom)
local p3 = map(3, steps, num, denom)
local p5 = map(5, steps, num, denom)
local p7 = map(7, steps, num, denom)
local p9 = map(9, steps, num, denom)
local p15 = map(15, steps, num, denom)
local p21 = map(21, steps, num, denom)
return (p9 == 2 * p3) and (p15 == p3 + p5) and (p21 == p3 + p7)
end
local function approx(steps, num, denom, intervals, title, prec, reduction, collapsed)
local thead
if collapsed then
thead = "{| class=\"wikitable center-all mw-collapsible mw-collapsed\"\n"
else
thead = "{| class=\"wikitable center-all\"\n"
end
local tpri = {"! colspan=\"2\" | Harmonic "}
local tabs = {"! rowspan=\"2\" | Error \n! [[Error|Absolute]] ([[cent|¢]]) "}
local trel = {"! [[Relative interval error|Relative]] ([[relative cent|%]]) "}
local tdeg
if reduction then
tdeg = {"! colspan=\"2\" | Steps<br />([[octave reduction|reduced]])"}
else
tdeg = {"! colspan=\"2\" | Step"}
end
local fmt_abs = string.format("%%+.%df", prec)
local fmt_rel = "%+.1f"
local equave = math.log(num/denom) / math.log(2)
for _, p in pairs(intervals) do
s = math.log(p) / math.log(num/denom)
v = s * steps
ev = math.floor(v + 0.5)
table.insert(tpri, "" .. p)
table.insert(tabs, "" .. string.gsub(string.format(fmt_abs, 1200 * equave * (ev - v ) / steps), "%-", "-"))
table.insert(trel, "" .. string.gsub(string.format(fmt_rel, 100 * (ev - v)), "%-", "-"))
if reduction then
table.insert(tdeg, "" .. ev .. "<br />(".. ev % steps .. ")")
else
table.insert(tdeg, "" .. ev)
end
end
local titleMarkup = ""
if title then
titleMarkup = "|+ style=\"font-size: 105%; white-space: nowrap;\" | " .. title .. "\n"
end
thead = thead ..
titleMarkup ..
"|-\n" ..
table.concat(tpri, "\n! ") .. "\n" ..
"|-\n" ..
table.concat(tabs, "\n| ") .. "\n" ..
"|-\n" ..
table.concat(trel, "\n| ") .. "\n" ..
"|-\n" ..
table.concat(tdeg, "\n| ") .. "\n" ..
"|}"
return thead
end
local intervals = {}
intervals.prime = {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, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251 }
intervals.odd = {3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127 }
intervals.integer = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128 }
-- 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 precision
local function prec_by_equal(steps, num, denom)
return math.floor(10 * math.log(steps * 1.9 * math.log(2) / math.log(num / denom)) / math.log(10)) / 10
end
function p.harmonics_in_equal (frame)
-- optional number of steps, default: 12
local steps = eval_num_arg(frame.args["steps"], 12)
-- numerator, default: 2
local num = eval_num_arg(frame.args["num"], 2)
-- denominator, default: 1
local denom = eval_num_arg(frame.args["denom"], 1)
-- optional number of columns, default: 11
local columns = eval_num_arg(frame.args["columns"], 11)
-- optional start column, default: start with prime 2
local start = eval_num_arg(frame.args["start"], 1)
-- option intervals
local select_intervals = "integer"
local name = steps .. "ed" .. num .. "/" .. denom
if denom == 1 then
if num == 2 then
name = steps .. "edo"
select_intervals = "odd"
if check_consistency(steps, num, denom) then
-- select_intervals = "prime_no2"
select_intervals = "prime"
end
elseif num == 3 then
name = steps .. "edt"
else
name = steps .. "ed" .. num
end
end
if num == 3 and denom == 2 then
name = steps .. "edf"
end
-- override default intervals
if frame.args["intervals"] and string.len(frame.args["intervals"]) > 0 then
select_intervals = frame.args["intervals"]
end
if select_intervals == "odd" then
title_intervals = "odd harmonics"
elseif select_intervals == "integer" then
title_intervals = "harmonics"
else
title_intervals = "prime harmonics"
end
local title = frame.args["title"]
if title == nil or string.len(title) == 0 then
title = "Approximation of ".. title_intervals .. " in " .. name
end
-- optional precision for abs error, default about 3 digits
local prec = eval_num_arg(frame.args["prec"], prec_by_equal(steps, num, denom))
local reduction = true
if steps == 1 then
reduction = false
end
local collapsed = utils.value_provided(frame.args["collapsed"])
local debugg = yesno(frame.args["debug"])
local result = approx(steps, num, denom, {unpack(intervals[select_intervals], start, start + columns - 1)}, title, prec, reduction, collapsed)
if debugg == true then
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
end
return frame:preprocess(result)
end
return p