Module:MOS intervals

Revision as of 03:20, 12 June 2024 by R-4981 (talk | contribs)
Module documentation[view] [edit] [history] [purge]
This module should not be invoked directly; use its corresponding template instead: Template:MOS intervals.

This module generates a table showing interval varieties and qualities for a given MOS scale.

Introspection summary for Module:MOS intervals 
Functions provided (2)
Line Function Params
14 _mos_intervals (main) (input_mos, mos_prefix, mos_abbrev)
140 mos_intervals (invokable) (frame)
Lua modules required (7)
Variable Module Functions used
et Module:ET dependency not used
interval_extension Module:Interval_extension harmonic_entropy_with_lookup_table
mos Module:MOS new
as_string
mode_to_step_matrix
brightest_mode
darkest_mode
equave_step_count
period_step_count
bright_gen_step_count
dark_gen_step_count
interval_eq
interval_to_cents
interval_as_string
parse
ord Module:Ordinal dependency not used
rat Module:Rational dependency not used
tamnams Module:TAMNAMS interval_quality
lookup_prefix
lookup_abbrev
utils Module:Utils dependency not used

No function descriptions were provided. The Lua code may have further information.


local mos = require('Module:MOS')
local rat = require('Module:Rational')
local ord = require('Module:Ordinal')
local utils = require('Module:Utils')
local et = require('Module:ET')
local tamnams = require('Module:TAMNAMS')
local interval_extension = require('Module:Interval_extension')
local p = {}

-- TODO:
-- - Convert tooltips to footnotes... somehow

-- Main function; to be called by wrapper
function p._mos_intervals(input_mos, mos_prefix, mos_abbrev)
	-- Default param for input mos is 5L 2s
	local input_mos = input_mos or mos.new(5, 2, 2)
	local mos_prefix = mos_prefix or "mos"
	local mos_abbrev = mos_abbrev or "m"
	
	-- Get the scale sig
	local scale_sig = mos.as_string(input_mos)
	
	-- Get the brightest and darkest modes as step matrices
	local bright_step_matrix = mos.mode_to_step_matrix(mos.brightest_mode(input_mos))
	local dark_step_matrix = mos.mode_to_step_matrix(mos.darkest_mode(input_mos))
	
	-- Get the number of steps per period and equave
	local equave_step_count = mos.equave_step_count(input_mos)
	local period_step_count = mos.period_step_count(input_mos)
	
	-- Get the step counts for the bright and dark generators
	local bright_gen_step_count = mos.bright_gen_step_count(input_mos)
	local dark_gen_step_count = mos.dark_gen_step_count(input_mos)
	
	-- Setup for harmonic entropy
	local step_ratios = {
		{ 4, 3 },
		{ 3, 2 },
		{ 5, 3 },
		{ 2, 1 },
		{ 5, 2 },
		{ 3, 1 },
		{ 4, 1 },
	}
	
	-- Create the table, starting with the headers
	local result = '{| class="wikitable"\n'
	result = result .. '|+ Intervals of ' .. scale_sig .. '\n'
	result = result .. '! colspan="3" | Intervals\n'
	result = result .. '! rowspan="2" | Steps subtended\n'
	result = result .. '! rowspan="2" | Range in cents\n'
	result = result .. '! rowspan="2" | [[Harmonic entropy]]<br/>(Shannon, <math>\\sqrt{n\\cdot d}</math>)\n'
	result = result .. '|-\n'
	result = result .. '! Generic<sup>[[#mosstep-1|[1]]]</sup>\n'
	result = result .. '! Specific<sup>[[#mosstep-2|[2]]]</sup>\n'
	result = result .. '! Abbrev.<sup>[[#mosstep-3|[3]]]</sup>\n'
	
	-- Write each row
	for i = 1, #bright_step_matrix do

		-- Compare the bright and dark intervals. If they're the same, then the
		-- current interval class is a period interval.
		local current_bright_interval = bright_step_matrix[i]
		local current_dark_interval = dark_step_matrix[i]
		local is_period = mos.interval_eq(current_bright_interval, current_dark_interval)
		
		-- If it's a period interval, then there is only one row to write.
		-- Otherwise, there are two rows to write, one for each size.
		if is_period then
			local cents = mos.interval_to_cents(current_bright_interval, input_mos, {1,1})
			
			result = result .. "|-\n"
			result = result .. string.format("| '''%s-%sstep'''\n", i-1, mos_prefix)
			result = result .. string.format("| %s\n" , tamnams.interval_quality(current_bright_interval, input_mos, "sentence-case"))
			result = result .. string.format("| %s\n" , tamnams.interval_quality(current_bright_interval, input_mos, "abbrev", "m"))
			result = result .. string.format("| %s\n" , mos.interval_as_string(current_bright_interval))
			result = result .. string.format("| %.1f¢\n", cents)
			result = result .. string.format("| %.6f nats\n", interval_extension.harmonic_entropy_with_lookup_table(cents))
		else
			-- Calculate the best and average harmonic entropies
			local he_dark_average = 0
			local he_bright_average = 0
			local he_dark_best = 1000
			local he_bright_best = 1000
			for i = 1, #step_ratios do
				local step_ratio = step_ratios[i]
				local he_dark = interval_extension.harmonic_entropy_with_lookup_table(mos.interval_to_cents(current_dark_interval, input_mos, step_ratio))
				local he_bright = interval_extension.harmonic_entropy_with_lookup_table(mos.interval_to_cents(current_bright_interval, input_mos, step_ratio))
				he_dark_average = he_dark_average + he_dark / #step_ratios
				he_bright_average = he_bright_average + he_bright / #step_ratios
				if he_dark_best > he_dark then
					he_dark_best = he_dark 
				end
				if he_bright_best > he_bright then
					he_bright_best = he_bright 
				end
			end
			
			-- Calculate the cent values min and max for the current intervals
			local sm_min_cents = mos.interval_to_cents(current_dark_interval, input_mos, {1,1})
			local sm_max_cents = mos.interval_to_cents(current_dark_interval, input_mos, {1,0})
			local lg_min_cents = mos.interval_to_cents(current_bright_interval, input_mos, {1,1})
			local lg_max_cents = mos.interval_to_cents(current_bright_interval, input_mos, {1,0})
			
			-- Then sort, as the min and max may be swapped 
			-- This happens if the dark interval has more small steps than large steps
			local dark_interval_range   = string.format("%.1f¢ to %.1f¢", math.min(sm_min_cents, sm_max_cents), math.max(sm_min_cents, sm_max_cents))
			local bright_interval_range = string.format("%.1f¢ to %.1f¢", math.min(lg_min_cents, lg_max_cents), math.max(lg_min_cents, lg_max_cents))
			
			result = result .. "|-\n"
			result = result .. string.format("| rowspan=\"2\" | %s-%sstep\n", i-1, mos_prefix)
			result = result .. string.format("| %s\n" , tamnams.interval_quality(current_dark_interval, input_mos, "sentence-case"))
			result = result .. string.format("| %s\n" , tamnams.interval_quality(current_dark_interval, input_mos, "abbrev", "m"))
			result = result .. string.format("| %s\n" , mos.interval_as_string(current_dark_interval))
			result = result .. string.format("| %s\n" , dark_interval_range)
			result = result .. string.format("| %.6f¢ nats (average), %.6f¢ nats (min)\n" , he_dark_average, he_dark_best)
			
			result = result .. "|-\n"
			result = result .. string.format("| %s\n" , tamnams.interval_quality(current_bright_interval, input_mos, "sentence-case"))
			result = result .. string.format("| %s\n" , tamnams.interval_quality(current_bright_interval, input_mos, "abbrev", "m"))
			result = result .. string.format("| %s\n" , mos.interval_as_string(current_bright_interval))
			result = result .. string.format("| %s\n" , bright_interval_range)
			result = result .. string.format("| %.6f¢ nats (average), %.6f¢ nats (min)\n" , he_bright_average, he_bright_best)
		end

	end
	
	result = result .. "|}\n"	
	result = result .. "<small>\n"
	result = result .. '# <span id="mosstep-1">Generic intervals are denoted solely by the number of steps they subtend.</span>\n'
	result = result .. '# <span id="mosstep-2">Specific intervals denote whether an interval is major, minor, augmented, perfect, or diminished.</span>\n'
	result = result .. '# <span id="mosstep-3">Abbreviations can be further shortened to &#39;ms&#39; if context allows.</span>\n'
	result = result .. "</small>"
	
	return result
	
end

-- Wrapper function; to be called by template
function p.mos_intervals(frame)
	-- Get input mos
	local input_mos = mos.parse(frame.args['Scale Signature'])
	
	-- Default param for mos prefix
	-- If "NONE" is given, no prefix will be used
	-- If left blank, try to find the appropriate mos prefix, or else defualt to "mos"
	-- If not left blank, use the prefix passed in instead
	local scale_sig = mos.as_string(input_mos)
	local mos_prefix = tamnams.lookup_prefix(input_mos)
	local mos_abbrev = tamnams.lookup_abbrev(input_mos)
	
	if frame.args['MOS Prefix'] == "NONE" then
		mos_prefix = ""
		mos_abbrev = ""
	elseif string.len(frame.args['MOS Prefix']) > 0 then
		mos_prefix = frame.args['MOS Prefix']
		mos_abbrev = frame.args['MOS Prefix']
	end
	
	if frame.args['MOS Abbrev'] == "NONE" then
		mos_abbrev = ""
	elseif string.len(frame.args['MOS Abbrev']) > 0 then
		mos_abbrev = frame.args['MOS Abbrev']
	end

	local result = p._mos_intervals(input_mos, mos_prefix, mos_abbrev)

	return result
end

return p