Module:Harmonics in equal: Difference between revisions

From Xenharmonic Wiki
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 = {'! colspan="2" | Harmonic '}
local thead
local tabs = {'! rowspan="2" | Error \n! absolute ([[cent|¢]]) '}
if collapsed then
local trel = {'! [[Relative interval error|relative]] (%) '}
thead = "{| class=\"wikitable center-all mw-collapsible mw-collapsed\"\n"
local tdeg = {'! colspan="2" | Step'}
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 = {'! colspan="2" | Steps<br>([[Octave_reduction|reduced]])'}
tdeg = {"! colspan=\"2\" | Steps<br />([[octave reduction|reduced]])"}
else
tdeg = {"! colspan=\"2\" | Step"}
end
end
local fmt_abs = string.format(' %%+.%df', prec)
local fmt_abs = string.format("%%+.%df", prec)
local fmt_rel = ' %+.0f'
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, '' .. p)
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, '' .. ev .. '<br>('.. ev % steps .. ')')
table.insert(tdeg, "" .. ev .. "<br />(".. ev % steps .. ")")
else
else
table.insert(tdeg, '' .. ev)
table.insert(tdeg, "" .. ev)
end
end
end
end
local titleMarkup = ''
local titleMarkup = ""
if title then
if title then
titleMarkup = '|-\n|+ ' .. title .. '\n'
titleMarkup = "|+ style=\"font-size: 105%; white-space: nowrap;\" | " .. title .. "\n"
end
end
return '{| class="wikitable center-all"\n' ..
thead = thead ..
titleMarkup ..
titleMarkup ..
'|-\n' ..
"|-\n" ..
table.concat(tpri, '\n!') .. '\n' ..
table.concat(tpri, "\n! ") .. "\n" ..
'|-\n' ..
"|-\n" ..
table.concat(tabs, '\n|') .. '\n' ..
table.concat(tabs, "\n| ") .. "\n" ..
'|-\n' ..
"|-\n" ..
table.concat(trel, '\n|') .. '\n' ..
table.concat(trel, "\n| ") .. "\n" ..
'|-\n' ..
"|-\n" ..
table.concat(tdeg, '\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, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27}
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) ~= 'number' then
if type(input) ~= "number" then
result = def_value
result = def_value
if type(input) == 'string' then
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['steps'], 12)
local steps = eval_num_arg(frame.args["steps"], 12)
-- numerator, default: 2
-- numerator, default: 2
local num = eval_num_arg(frame.args['num'], 2)
local num = eval_num_arg(frame.args["num"], 2)
-- denominator, default: 1
-- denominator, default: 1
local denom = eval_num_arg(frame.args['denom'], 1)
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['columns'], 11)
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['start'], 1)
local start = eval_num_arg(frame.args["start"], 1)
-- option intervals
-- option intervals
local select_intervals = "integer"
local select_intervals = "integer"


local name = steps .. 'ed' .. num .. '/' .. denom
local name = steps .. "ed" .. num .. "/" .. denom
if denom == 1 then
if denom == 1 then
if num == 2 then
if num == 2 then
name = steps .. 'edo'
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 .. 'edt'
name = steps .. "edt"
else
else
name = steps .. 'ed' .. num
name = steps .. "ed" .. num
end
end
end
end
if num == 3 and denom == 2 then
if num == 3 and denom == 2 then
name = steps .. 'edf'
name = steps .. "edf"
end
end
-- override default intervals
-- override default intervals
if frame.args['intervals'] and string.len(frame.args['intervals']) > 0 then
if frame.args["intervals"] and string.len(frame.args["intervals"]) > 0 then
select_intervals = frame.args['intervals']
select_intervals = frame.args["intervals"]
end
end
title_intervals = "prime harmonics"
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['title']
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['prec'], prec_by_equal(steps, num, denom))
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
return approx( steps, num, denom, {unpack(intervals[select_intervals], start, start+columns-1)}, title, prec, reduction)
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

Module documentation[view] [edit] [history] [purge]
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 
Functions provided (1)
Line Function Params
100 harmonics_in_equal (invokable) (frame)
Lua modules required (2)
Variable Module Functions used
utils Module:Utils value_provided
yesno Module:yesno yesno

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