Module:Temperament data: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
CompactStar (talk | contribs)
No edit summary
ArrowHead294 (talk | contribs)
mNo edit summary
 
(45 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local rat = require('Module:Rational')
local p = {}
local p = {}
local u = require('Module:Utils')


local function gcd(a,b)
local rat = require("Module:Rational")
  if type(a) == "number" and type(b) == "number" and  
local u = require("Module:Utils")
        a == math.floor(a) and b == math.floor(b) then
 
    if b == 0 then
local function gcd(a, b)
      return a
    if type(a) == "number" and type(b) == "number" and a == math.floor(a) and b == math.floor(b) then
        if b == 0 then
            return a
        else
            return gcd(b, a % b) -- tail recursion
        end
     else
     else
      return gcd(b, a % b) -- tail recursion
        error("Invalid argument to gcd (" .. tostring(a) .. ", " .. tostring(b) .. ")", 2)
     end
     end
  else
    error("Invalid argument to gcd (" .. tostring(a) .. "," ..
          tostring(b) .. ")", 2)
  end
end
end


Line 68: Line 67:


local function matinv(a)
local function matinv(a)
local dbl_identity = {}
local xn = scalarmatmul(a, 1e-7)
for i = 1, #a do
 
dbl_identity[i] = {}
for i = 1, 75 do
for j = 1, #a do
xn = matsub(scalarmatmul(xn, 2), matmul(xn, matmul(a, xn)))
if i == j then
dbl_identity[i][j] = 2
else
dbl_identity[i][j] = 0
end
end
end
end
local xn = scalarmatmul(a, 0.000005)
for i = 1, 30 do
xn = matmul(xn, matsub(dbl_identity, matmul(a, xn)))
end
return xn
return xn
end
end
Line 140: Line 128:
end
end


local function get_te_tuning_map(subgroup, comma_basis)
local function get_reduced_mapping(comma_basis, preimage)
local v = unreduced_mapping_from_basis(comma_basis)
a =  pseudoinv(matmul(unreduced_mapping_from_basis(comma_basis), preimage))
for i = 1, #a do
for j = 1, #a[1] do
a[i][j] = math.floor(a[i][j] + 0.5) -- round each entry (they are not exact integers)
end
end
return a
end
 
local function get_te_generator(subgroup, comma_basis, preimage)
local v = get_reduced_mapping(comma_basis, preimage)
local w = {}
local w = {}
for i = 1, #subgroup do
for i = 1, #subgroup do
Line 147: Line 145:
for j = 1, #subgroup do
for j = 1, #subgroup do
if i == j then
if i == j then
w[i][j] = math.log(2)/math.log(subgroup[i])
w[i][j] = math.log(2) / math.log(subgroup[i])
else
else
w[i][j] = 0
w[i][j] = 0
Line 163: Line 161:
end
end


local function get_te_generator(subgroup, comma_basis, preimage)
return matmul(get_te_tuning_map(subgroup, comma_basis), preimage)
end


local function get_pote_generator(subgroup, comma_basis, preimage)
local function get_pote_generator(subgroup, comma_basis, preimage)
Line 172: Line 167:
period = period * (subgroup[i]^preimage[i][1])
period = period * (subgroup[i]^preimage[i][1])
end
end
mw.logObject(period)
local te = get_te_generator(subgroup, comma_basis, preimage)
local te = get_te_generator(subgroup, comma_basis, preimage)
local stretch_factor = te[1][1] * math.log(period) / math.log(2)
local stretch_factor = te[1][1] * math.log(2) / math.log(period)
return scalarmatmul(te, stretch_factor)
return scalarmatmul(te, 1 / stretch_factor)
end
end


-- Parsing/display functions
-- Parsing/display functions
Line 222: Line 218:
local function mysplit (inputstr, sep)
local function mysplit (inputstr, sep)
         if sep == nil then
         if sep == nil then
                sep = "%s"
    sep = "%s"
         end
         end
         local t={}
         local t = {}
         for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
         for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
                 table.insert(t, str)
                 table.insert(t, str)
         end
         end
Line 239: Line 235:


function p.temperament_data(frame)
function p.temperament_data(frame)
local subgroup = mysplit(frame.args['subgroup'], ".")
local subgroup = mysplit(frame.args["subgroup"], ".")
for i = 1, #subgroup do
for i = 1, #subgroup do
subgroup[i] = tonumber(subgroup[i])
subgroup[i] = tonumber(subgroup[i])
end
end
local comma_matrix = mysplit(frame.args['comma_list'], ",")
local comma_matrix = mysplit(frame.args["comma_list"], ",")
for i = 1, #comma_matrix do
for i = 1, #comma_matrix do
comma_matrix[i] = rat.parse(comma_matrix[i])
comma_matrix[i] = rat.parse(comma_matrix[i])
end
end
comma_matrix = rat_list_to_matrix(subgroup, comma_matrix)
comma_matrix = rat_list_to_matrix(subgroup, comma_matrix)
local unparsed_gens = mysplit(frame.args['generators'], ",")
local unparsed_gens = mysplit(frame.args["generators"], ",")
local generators =  mysplit(frame.args['generators'], ",")
local generators =  mysplit(frame.args["generators"], ",")
for i = 1, #generators do
for i = 1, #generators do
generators[i] = rat.parse(generators[i])
generators[i] = rat.parse(generators[i])
end
end
generators = rat_list_to_matrix(subgroup, generators)
generators = rat_list_to_matrix(subgroup, generators)
local mapping = get_reduced_mapping(comma_matrix, generators)
local cte_generator = frame.args["cte_generator"]
local pote_generator = get_pote_generator(subgroup, comma_matrix, generators)
local pote_generator = get_pote_generator(subgroup, comma_matrix, generators)
local result =  "[[Subgroup]]: " .. frame.args['subgroup']
local result =  "[[Subgroup]]: " .. frame.args["subgroup"]
result = result .. "\n\n[[Comma list]]: " .. frame.args['comma_list']
result = result .. "\n\n[[Comma list]]: " .. frame.args["comma_list"]
result = result .. "\n\n[[Mapping]]: [⟨"
for i = 1, #mapping do
for j = 1, #mapping[1] do
result = result .. mapping[i][j] .. " "
end
result = result:sub(0,-2) .. "], ⟨"
end
result = result:sub(0,-6) .. "]"
result = result .. "\n\n: mapping generators: "
result = result .. "\n\n: mapping generators: "
for i = 1, #unparsed_gens do
for i = 1, #unparsed_gens do
result = result .. '~' .. trim(unparsed_gens[i]) .. ", "
result = result .. "~" .. trim(unparsed_gens[i]) .. ", "
end
result = result:sub(0, -3)
if cte_generator ~= "" then
cte_generator = mysplit(cte_generator, ",")
for i = 1, #cte_generator do
cte_generator[i] = tonumber(cte_generator[i])
end
result = result .. "\n\n[[Optimal tuning]]s:\n* [[CTE]]:"
for i = 1, #(cte_generator) do
result = result .. "~" .. trim(unparsed_gens[i]) .. " = "
if subgroup[1] == 2 and i == 1 then
result = result .. "1\\1"
elseif subgroup[1] == 3 and i == 1 then
result = result .. "1\\1edt"
else
result = result .. u._round(cte_generator[i], 7)
end
result = result .. ", "
end
result = result:sub(0,-3)
if subgroup[1] ~= 2 then
result = result .. "\n* [[Lp tuning|POL2]]:"
else
result = result .. "\n* [[POTE]]:"
end
else
if subgroup[1] ~= 2 then
result = result .. "\n\n[[Optimal tuning]] ([[Lp tuning|POL2]]): "
else
result = result .. "\n\n[[Optimal tuning]] ([[POTE]]): "
end
end
end
result = result:sub(0,-3)
result = result .. "\n\n[[Optimal tuning]]s:\n*[[POTE]]: "
for i = 1, #(pote_generator[1]) do
for i = 1, #(pote_generator[1]) do
if pote_generator[1][i] == 1 then
result = result .. "~" .. trim(unparsed_gens[i]) .. " = "
result = result .. '~' .. trim(unparsed_gens[i]) ..  ' = ' .. '1\\[[1edo]]' .. ', '
if subgroup[1] == 2 and i == 1 then
result = result .. "1\\1"
elseif subgroup[1] == 3 and i == 1 then
result = result .. "1\\1edt"
else
result = result .. u._round(pote_generator[1][i] * 1200, 7)
end
end
result = result .. '~' .. trim(unparsed_gens[i]) .. ' = ' .. u._round(pote_generator[1][i] * 1200, 7) .. ', '
result = result .. ", "
end
end
result = result:sub(0,-3)
result = result:sub(0,-3)
return result
return result
end
end


return p
return p

Latest revision as of 18:22, 19 May 2025

Module documentation[view] [edit] [history] [purge]
Note: Do not invoke this module directly; use the corresponding template instead: Template:Temperament data.
Todo: add documentation

local p = {}

local rat = require("Module:Rational")
local u = require("Module:Utils")

local function gcd(a, b)
    if type(a) == "number" and type(b) == "number" and a == math.floor(a) and b == math.floor(b) then
        if b == 0 then
            return a
        else
            return gcd(b, a % b) -- tail recursion
        end
    else
        error("Invalid argument to gcd (" .. tostring(a) .. ", " .. tostring(b) .. ")", 2)
    end
end

-- Linear algebra and RTT functions

local function matadd(a, b)
	local result = {}
	for i = 1, #a  do
		result[i] = {}
		for j = 1, #(b[1]) do
			result[i][j] = a[i][j] + b[i][j]
		end
	end
	return result
end

local function matsub(a, b)
	local result = {}
	for i = 1, #a  do
		result[i] = {}
		for j = 1, #(b[1]) do
			result[i][j] = a[i][j] - b[i][j]
		end
	end
	return result
end

local function matmul(a, b)
	local result = {}
	for i = 1, #a  do
		result[i] = {}
		for j = 1, #(b[1]) do
			result[i][j] = 0
			for k = 1, #(a[1]) do
				result[i][j] = result[i][j] + (a[i][k] * b[k][j])
			end
		end
	end
	return result
end


local function scalarmatmul(a, b)
	local result = {}
	for i = 1, #a  do
		result[i] = {}
		for j = 1, #(a[1]) do
			result[i][j] = a[i][j] * b
		end
	end
	return result
end

local function matinv(a)
	local xn = scalarmatmul(a, 1e-7)

	for i = 1, 75 do
		xn = matsub(scalarmatmul(xn, 2), matmul(xn, matmul(a, xn)))
	end
	
	return xn
end

local function transpose(a)
	local result = {}
	for i = 1, #a[1] do
		result[i] = {}
		for j = 1, #a do
			result[i][j] = a[j][i]
		end
	end
	return result
end

local function antitranspose(a)
	local result = {}
	for i = 1, #a[1] do
		result[i] = {}
		for j = 1, #a do
			result[i][j] = a[#a - j + 1][#a[1] - i + 1]
		end
	end
	return result
end


local function pseudoinv(a)
	return matmul(transpose(a), matinv(matmul(a, transpose(a))))
end

local function nullspace(mapping)
	local identity = {}
	for i = 1, #mapping[1] do
		identity[i] = {}
		for j = 1, #mapping[1] do
			if i == j then
				identity[i][j] = 1
			else
				identity[i][j] = 0
			end
		end
	end

	-- local w = {{0},{1},{0}}
	-- for i = 1, #mapping[1] do
	-- 	w[i] = {10}
	-- end

	return matsub(identity, matmul(pseudoinv(mapping), mapping))
end

local function unreduced_mapping_from_basis(comma_basis)
	return antitranspose(nullspace(antitranspose(comma_basis)))
end

local function get_reduced_mapping(comma_basis, preimage)
	a =  pseudoinv(matmul(unreduced_mapping_from_basis(comma_basis), preimage))
	for i = 1, #a do
		for j = 1, #a[1] do
			a[i][j] = math.floor(a[i][j] + 0.5) -- round each entry (they are not exact integers)
		end
	end
	return a
end

local function get_te_generator(subgroup, comma_basis, preimage)
	local v = get_reduced_mapping(comma_basis, preimage)
	local w = {}
	for i = 1, #subgroup do
		w[i] = {}
		for j = 1, #subgroup do
			if i == j then
				w[i][j] = math.log(2) / math.log(subgroup[i])
			else
				w[i][j] = 0
			end
		end
	end
	
	local jw = {{}}
	for i = 1, #subgroup do
		jw[1][i] = 1
	end
	local vw = matmul(v, w)
	local g = matmul(jw, pseudoinv(vw))
	return g
end


local function get_pote_generator(subgroup, comma_basis, preimage)
	local period = 1
	for i = 1, #subgroup do
		period = period * (subgroup[i]^preimage[i][1])
	end
	local te = get_te_generator(subgroup, comma_basis, preimage)
	local stretch_factor = te[1][1] * math.log(2) / math.log(period)
	return scalarmatmul(te, 1 / stretch_factor)
end



-- Parsing/display functions

local function int_to_subgroup_monzo(subgroup, x) 
	local result = {}
	local x2 = x
	for i = 1, #subgroup do
		result[i] = 0
		while true do
			x2 = x2 / subgroup[i]
			if x2 ~= math.floor(x2) then
				break
			end
			result[i] = result[i] + 1
		end
		x2 = x
	end
	
	return result
end


local function rat_to_subgroup_monzo(subgroup, x)
	local n, d = rat.as_pair(x)
	return matsub({int_to_subgroup_monzo(subgroup, n)}, {int_to_subgroup_monzo(subgroup, d)})[1]	
end

local function rat_list_to_matrix(subgroup, list)
	local result = {}
	for j = 1, #subgroup do
		result[j] = {}
	end
	
	for i = 1, #list do
		local smonzo = rat_to_subgroup_monzo(subgroup, list[i])
		for j = 1, #subgroup do
			result[j][i] = smonzo[j]
		end
	end
	
	return result
end

local function mysplit (inputstr, sep)
        if sep == nil then
    		sep = "%s"
        end
        local t = {}
        for str in string.gmatch(inputstr, "([^" .. sep .. "]+)") do
                table.insert(t, str)
        end
        return t
end

local function trim(x)
	local str = x
	str = str:gsub("%s+", "")
    str = string.gsub(str, "%s+", "")
    return str
end

function p.temperament_data(frame)
	local subgroup = mysplit(frame.args["subgroup"], ".")
	for i = 1, #subgroup do
		subgroup[i] = tonumber(subgroup[i])
	end
	local comma_matrix = mysplit(frame.args["comma_list"], ",")
	for i = 1, #comma_matrix do
		comma_matrix[i] = rat.parse(comma_matrix[i])
	end
	comma_matrix = rat_list_to_matrix(subgroup, comma_matrix)
	local unparsed_gens = mysplit(frame.args["generators"], ",")
	local generators =  mysplit(frame.args["generators"], ",")
	for i = 1, #generators do
		generators[i] = rat.parse(generators[i])
	end
	generators = rat_list_to_matrix(subgroup, generators)
	local mapping = get_reduced_mapping(comma_matrix, generators)
	local cte_generator = frame.args["cte_generator"]
	local pote_generator = get_pote_generator(subgroup, comma_matrix, generators)
	local result =  "[[Subgroup]]: " .. frame.args["subgroup"]
	result = result .. "\n\n[[Comma list]]: " .. frame.args["comma_list"]
	result = result .. "\n\n[[Mapping]]: [⟨"
	for i = 1, #mapping do
		for j = 1, #mapping[1] do
			result = result .. mapping[i][j] .. " "
		end
		result = result:sub(0,-2) .. "], ⟨"
	end	
	result = result:sub(0,-6) .. "]"
	
	result = result .. "\n\n: mapping generators: "
	for i = 1, #unparsed_gens do
		result = result .. "~" .. trim(unparsed_gens[i]) .. ", "
	end
	result = result:sub(0, -3)
	if cte_generator ~= "" then
		cte_generator = mysplit(cte_generator, ",")
		for i = 1, #cte_generator do
			cte_generator[i] = tonumber(cte_generator[i])
		end
		
		result = result .. "\n\n[[Optimal tuning]]s:\n* [[CTE]]:"
		
		for i = 1, #(cte_generator) do
			result = result .. "~" .. trim(unparsed_gens[i]) .. " = "
			if subgroup[1] == 2 and i == 1 then
				result = result .. "1\\1"
			elseif subgroup[1] == 3 and i == 1 then
				result = result .. "1\\1edt"
			else
				result = result .. u._round(cte_generator[i], 7)
			end
			result = result .. ", "
		end
		
		result = result:sub(0,-3)
	
		if subgroup[1] ~= 2 then
			result = result .. "\n* [[Lp tuning|POL2]]:"
		else
			result = result .. "\n* [[POTE]]:"
		end
	else
		if subgroup[1] ~= 2 then
			result = result .. "\n\n[[Optimal tuning]] ([[Lp tuning|POL2]]): "
		else
			result = result .. "\n\n[[Optimal tuning]] ([[POTE]]): "
		end
	end
	
	for i = 1, #(pote_generator[1]) do
		result = result .. "~" .. trim(unparsed_gens[i]) .. " = "
		if subgroup[1] == 2 and i == 1 then
			result = result .. "1\\1"
		elseif subgroup[1] == 3 and i == 1 then
			result = result .. "1\\1edt"
			else
			result = result .. u._round(pote_generator[1][i] * 1200, 7)
		end
		result = result .. ", "
	end
	result = result:sub(0,-3)
	
	return result
end

return p