Module:Temperament data: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
CompactStar (talk | contribs)
No edit summary
CompactStar (talk | contribs)
No edit summary
Line 4: Line 4:


local function gcd(a,b)
local function gcd(a,b)
   if type(a) == "number" and type(b) == "number" and  
   if type(a) == 'number' and type(b) == 'number' and  
         a == math.floor(a) and b == math.floor(b) then
         a == math.floor(a) and b == math.floor(b) then
     if b == 0 then
     if b == 0 then
Line 12: Line 12:
     end
     end
   else
   else
     error("Invalid argument to gcd (" .. tostring(a) .. "," ..  
     error('Invalid argument to gcd (' .. tostring(a) .. ',' ..  
           tostring(b) .. ")", 2)
           tostring(b) .. ')', 2)
   end
   end
end
end
Line 222: Line 222:
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 233: Line 233:
local function trim(x)
local function trim(x)
local str = x
local str = x
str = str:gsub("%s+", "")
str = str:gsub('%s+', '')
     str = string.gsub(str, "%s+", "")
     str = string.gsub(str, '%s+', '')
     return str
     return str
end
end


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])
Line 255: Line 255:
generators = rat_list_to_matrix(subgroup, generators)
generators = rat_list_to_matrix(subgroup, generators)
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 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
end
result = result:sub(0,-3)
result = result:sub(0,-3)
result = result .. "\n\n[[Optimal tuning]]s:\n*[[POTE]]: "
if subgroup[1] ~= 2 then
result = result .. '\n\n[[Optimal tuning]]s:\n*[[POTE|Pure-' .. subgroup[1] .. '/1 TE]]'
else
result = result .. '\n\n[[Optimal tuning]]s:\n*[[POTE]]: '
end
for i = 1, #(pote_generator[1]) do
for i = 1, #(pote_generator[1]) do
if pote_generator[1][i] == 1 then
if pote_generator[1][i] == 1 then

Revision as of 04:06, 17 March 2024

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 rat = require('Module:Rational')
local p = {}
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 dbl_identity = {}
	for i = 1, #a do
		dbl_identity[i] = {}
		for j = 1, #a do
			if i == j then
				dbl_identity[i][j] = 2
			else
				dbl_identity[i][j] = 0
			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
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_te_tuning_map(subgroup, comma_basis)
	local v = unreduced_mapping_from_basis(comma_basis)
	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_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 period = 1
	for i = 1, #subgroup do
		period = period * (subgroup[i]^preimage[i][1])
	end
	mw.logObject(period)
	local te = get_te_generator(subgroup, comma_basis, preimage)
	local stretch_factor = te[1][1] * math.log(period) / math.log(2)
	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 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 generators: '
	for i = 1, #unparsed_gens do
		result = result .. '~' .. trim(unparsed_gens[i]) .. ', '
	end
	result = result:sub(0,-3)
	if subgroup[1] ~= 2 then
		result = result .. '\n\n[[Optimal tuning]]s:\n*[[POTE|Pure-' .. subgroup[1] .. '/1 TE]]'
	else
		result = result .. '\n\n[[Optimal tuning]]s:\n*[[POTE]]: '
	end
	for i = 1, #(pote_generator[1]) do
		if pote_generator[1][i] == 1 then
			result = result .. '~' .. trim(unparsed_gens[i]) ..  ' = ' .. '1\\1' .. ', '
		else
			result = result .. '~' .. trim(unparsed_gens[i]) .. ' = ' .. u._round(pote_generator[1][i] * 1200, 7) .. ', '
		end
	end
	result = result:sub(0,-3)
	return result
end


return p