Module:MOS tunings: Difference between revisions
Jump to navigation
Jump to search
m max tolerance within a cent value is now 30 |
Comments; simplify some code; removed unnecessary modules |
||
| Line 2: | Line 2: | ||
local tamnams = require("Module:TAMNAMS") | local tamnams = require("Module:TAMNAMS") | ||
local et = require("Module:ET") | local et = require("Module:ET") | ||
local yesno = require("Module:Yesno") | local yesno = require("Module:Yesno") | ||
local tip = require("Module:Template input parse") | local tip = require("Module:Template input parse") | ||
local jira = require("Module:JI ratios") | local jira = require("Module:JI ratios") | ||
local p = {} | local p = {} | ||
| Line 113: | Line 111: | ||
-- Calculate the tolerance, the range in which ratios can be accepted +/- | -- Calculate the tolerance, the range in which ratios can be accepted +/- | ||
-- from an et-step. (ET may be a non-integer value.) | -- from an et-step. (ET may be a non-integer value, since the L:s ratio is | ||
-- normalized to x:1.) | |||
-- Tolerance is how many cents away from an et-step a ratio can be. This is | |||
-- 30% of the small step size, and maxes out at 30 cents. | |||
local steps_in_et = input_mos.nL * avg_step_ratio[1] + input_mos.ns * avg_step_ratio[2] | local steps_in_et = input_mos.nL * avg_step_ratio[1] + input_mos.ns * avg_step_ratio[2] | ||
local tolerance = math.min(( | local tolerance = math.min((mos.equave_to_cents(input_mos) / steps_in_et) * 0.30, 30) | ||
-- Calculate the cent values for each interval in the modal union | -- Calculate the cent values for each interval in the modal union | ||
Revision as of 19:03, 30 August 2024
- This module should not be invoked directly; use its corresponding template instead: Template:MOS tunings.
This module displays the cent values of a MOS scale's degrees under various tunings, or step ratios, as well as the nearest JI ratios represented by those scale degrees.
| Introspection summary for Module:MOS tunings | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||||||||||||||||||||||||||||||||||||||
No function descriptions were provided. The Lua code may have further information.
local mos = require("Module:MOS")
local tamnams = require("Module:TAMNAMS")
local et = require("Module:ET")
local yesno = require("Module:Yesno")
local tip = require("Module:Template input parse")
local jira = require("Module:JI ratios")
local p = {}
-- Rewritten/simplified module replacement for Module:MOS degrees
-- A new template is chosen because it's a better name than the old one and is
-- far easier to maintain than the old one.
-- TODO:
-- - Add option to enter a custom set of ratios; requires sorting
-- - Add option to omit JI col altogether
-- - Diatonic interval category lookup?
-- Helper function
-- Capitalizes the first character of a string
function p.capitalize_first(text)
return string.upper(string.sub(text, 1, 1)) .. string.sub(text, 2, -1)
end
-- Helper function
-- Sorts step ratios L:s by their hardnesses
function p.sort_step_ratios(step_ratios)
if #step_ratios < 2 then
return step_ratios
end
-- Sort using selection sort, which is ok for smol datasets.
for i = 1, #step_ratios - 1 do
local index_of_smallest = i
local current_val = step_ratios[i][1] / step_ratios[i][2]
-- Find the ratio with the smallest hardness
for j = i + 1, #step_ratios do
if (step_ratios[j][1] / step_ratios[j][2] < current_val) then
index_of_smallest = j
end
end
if index_of_smallest ~= i then
local temp = step_ratios[index_of_smallest]
step_ratios[index_of_smallest] = step_ratios[i]
step_ratios[i] = temp
end
end
return step_ratios
end
-- Helper function
-- Finds the step ratio range and sorts step ratios
function p.preprocess_step_ratios(step_ratios)
local step_ratios = p.sort_step_ratios(step_ratios)
local step_ratio_range = ""
-- If the step ratios are 3/2, 2/1, and 3/1 in that order, then they are
-- the simple step ratios: basic, hard, and soft.
-- These should not be sorted, since the basic-hard-soft sorting is a little
-- more intuitive than sorting by hardness.
if #step_ratios == 3 then
if step_ratios[1][1] == 3 and step_ratios[1][2] == 2
and step_ratios[2][1] == 2 and step_ratios[2][2] == 1
and step_ratios[3][1] == 3 and step_ratios[3][2] == 1 then
return "Simple Tunings", {{2,1}, {3,1}, {3,2}}
end
end
-- If there are multiple step ratios, find the step ratio range it
-- corresponds to. If there is one step ratio, find the name of that
-- hardness. If there are zero step ratios, then return "Tunings"
if #step_ratios > 1 then
local lower_ratio = step_ratios[1]
local upper_ratio = step_ratios[#step_ratios]
step_ratio_range = tamnams.find_step_ratio_range_for_ratio_pair(lower_ratio, upper_ratio)
if step_ratio_range ~= nil then
step_ratio_range = p.capitalize_first(step_ratio_range) .. " Tunings"
else
step_ratio_range = "Tunings"
end
elseif #step_ratios == 1 then
step_ratio_range = tamnams.lookup_step_ratio(step_ratios[1])
if step_ratio_range ~= nil then
step_ratio_range = p.capitalize_first(step_ratio_range) .. " Tuning"
else
step_ratio_range = string.format("%s/%s", step_ratios[1][1], step_ratios[1][2]) .. " Tuning"
end
else
step_ratio_range = "Tunings"
end
return step_ratio_range, step_ratios
end
-- Preprocess step ratios
function p.preprocess_ji_ratios(input_mos, modal_union, step_ratios, ji_ratios)
-- Calculate the avegrage step ratio, then normalize it to x:1
local avg_step_ratio = {0, 0}
for i = 1, #step_ratios do
avg_step_ratio[1] = avg_step_ratio[1] + step_ratios[i][1]
avg_step_ratio[2] = avg_step_ratio[2] + step_ratios[i][2]
end
avg_step_ratio[1] = avg_step_ratio[1] / #step_ratios
avg_step_ratio[2] = avg_step_ratio[2] / #step_ratios
avg_step_ratio[1] = avg_step_ratio[1] / avg_step_ratio[2]
avg_step_ratio[2] = avg_step_ratio[2] / avg_step_ratio[2]
-- Calculate the tolerance, the range in which ratios can be accepted +/-
-- from an et-step. (ET may be a non-integer value, since the L:s ratio is
-- normalized to x:1.)
-- Tolerance is how many cents away from an et-step a ratio can be. This is
-- 30% of the small step size, and maxes out at 30 cents.
local steps_in_et = input_mos.nL * avg_step_ratio[1] + input_mos.ns * avg_step_ratio[2]
local tolerance = math.min((mos.equave_to_cents(input_mos) / steps_in_et) * 0.30, 30)
-- Calculate the cent values for each interval in the modal union
local cent_values = {}
for i = 1, #modal_union do
table.insert(cent_values, mos.interval_to_cents(modal_union[i], input_mos, avg_step_ratio))
end
local sorted_ratios = jira.sort_by_closeness_to_cent_values(ji_ratios, cent_values, tolerance)
return sorted_ratios, string.format("Ratios within %.1f of the averaged scale degrees are shown.", tolerance)
end
-- Main function
function p._mos_tunings(input_mos, mos_prefix, mos_abbrev, step_ratios, ji_ratios, search_params)
local input_mos = input_mos or mos.new(5,2)
local mos_prefix = mos_prefix or "mos"
local mos_abbrev = mos_abbrev or "m"
local step_ratios = step_ratios or { {2,1}, {3,1}, {3,2} }
local search_params = {["Int Limit"] = 25, ["Tenney Height"] = 9, ["Prime Limit"] = 11}
local modal_union = mos.modal_union(input_mos)
local scale_sig = mos.as_string(input_mos)
-- Sort/preprocess step ratios
local step_ratio_range = ""
step_ratio_range, step_ratios = p.preprocess_step_ratios(step_ratios)
-- Preprocess JI ratios
local equave_in_cents = mos.equave_to_cents(input_mos)
local ji_ratios = ji_ratios or jira.search_by_params(search_params, mos.equave_to_cents(input_mos))
local sorted_ji_ratios, search_info = p.preprocess_ji_ratios(input_mos, modal_union, step_ratios, ji_ratios)
-- Create table
local result = "{| class=\"wikitable sortable right-all left-1 left-2\"\n"
-- Table caption
result = result .. string.format("|+ style=\"font-size: 105%%; white-space: nowrap;\" | %s of %s\n", step_ratio_range, scale_sig)
-- First row of headers
-- First two headers span two rows
result = result .. "! rowspan=\"2\" class=\"unsortable\" | Scale degree\n"
result = result .. "! rowspan=\"2\" class=\"unsortable\" | Abbrev.\n"
-- Headers for tunings; these span two cols
for i = 1, #step_ratios do
local step_ratio_as_text = tamnams.lookup_step_ratio(step_ratios[i])
if step_ratio_as_text == nil then
step_ratio_as_text = string.format("%s:%s", step_ratios[i][1], step_ratios[i][2])
else
step_ratio_as_text = p.capitalize_first(step_ratio_as_text) .. string.format(" (%s:%s)", step_ratios[i][1], step_ratios[i][2])
end
local et_as_string = et.as_string(mos.mos_to_et(input_mos, step_ratios[i]))
local header_text = string.format("%s<br>[[%s]]", step_ratio_as_text, et_as_string)
result = result .. string.format("! colspan=\"2\" | %s\n", header_text)
end
-- Headers for JI ratios; this spans two rows
result = result .. "! rowspan=\"2\" class=\"unsortable\" | Approx.<br>Ratios\n"
result = result .. "|-\n"
-- Second row of headers
for i = 1, #step_ratios do
result = result .. "! Steps\n"
result = result .. "! Cents\n"
end
-- Add a row for each scale degree
for i = 1, #modal_union do
local interval = modal_union[i]
-- Add cells for the degree names
local degree_name = tamnams.degree_quality(interval, input_mos, "sentence-case", mos_prefix)
local degree_abbrev = tamnams.degree_quality(interval, input_mos, "abbrev" , mos_abbrev)
result = result
.. "|-\n"
.. string.format("| %s || %s", degree_name, degree_abbrev)
-- Add cells for each interval's tunings
for j = 1, #step_ratios do
local step_ratio = step_ratios[j]
local step_count = mos.interval_to_et_steps(interval, step_ratio)
local cents = mos.interval_to_cents(interval, input_mos, step_ratio)
result = result
.. " "
.. string.format("|| %s\\%s || %.1f", step_count, input_mos.nL * step_ratio[1] + input_mos.ns * step_ratio[2], cents)
end
-- Add cells for JI ratios
local ratios_as_text = jira.ratios_as_text(sorted_ji_ratios[i])
result = result .. string.format("|| %s", ratios_as_text)
result = result .. "\n"
end
-- End of table
result = result .. "|}\n" .. search_info
return result
end
-- Wrapper function; to be called by template
function p.mos_tunings(frame)
-- Get params
local scalesig = frame.args["Scale Signature"]
local mos_prefix = frame.args["MOS Prefix"]
local mos_abbrev = frame.args["MOS Abbrev"]
local collapsed = yesno(frame.args["Collapsed"], false) -- Currently does nothing
local step_ratios = tip.parse_numeric_pairs(frame.args["Step Ratios"]) or (frame.args["Step Ratios"] == "Central Spectrum" and {{4,3}, {3,2}, {5,3}, {2,1}, {5,2}, {3,1}, {4,1}} or {{2,1}, {3,1}, {3,2}})
local ji_ratios = tip.parse_numeric_pairs(frame.args["JI Ratios"] ) or {{1,1}, {3,2}, {2,1}}
-- Parse scalesig
local input_mos = mos.parse(scalesig)
-- Verify name/prefix/abbrev
mos_prefix = tamnams.verify_prefix(input_mos, mos_prefix)
mos_abbrev = tamnams.verify_abbrev(input_mos, mos_abbrev)
return p._mos_tunings(input_mos, mos_prefix, mos_abbrev, step_ratios)
end
function p.tester()
local range, ratios = p.preprocess_step_ratios({ {7,1}, {3,1}, {2,1} })
local input_mos = mos.parse("9L 4s<7/2>")
--return p.preprocess_ji_ratios(input_mos, mos.modal_union(input_mos), {{2,1}, {3,2}, {5,3}}, ji_ratios)
--return ji_ratios
return p._mos_tunings(input_mos)
end
return p