Module:MOS intervals: Difference between revisions

From Xenharmonic Wiki
Jump to navigation Jump to search
ArrowHead294 (talk | contribs)
m Non-wrap steps subtended section
Ganaram inukshuk (talk | contribs)
add experimental interval region lookup; may move to a different module later
Line 6: Line 6:
local tamnams = require("Module:TAMNAMS")
local tamnams = require("Module:TAMNAMS")
local yesno = require("Module:Yesno")
local yesno = require("Module:Yesno")
-- EXPERIMENTAL FEATURE: lookup table for intervals
-- Mostly based off Margo Schulter's categories (without large/medium/small),
-- but other interpretations are possible. Plus, this only goes up to 1200c.
interval_ranges = {
    { name = "Pure Unison (1:1)", range = {0, 0} },
    { name = "Commas", range = {0, 30} },
    { name = "Dieses", range = {30, 60} },
    { name = "Minor Seconds", range = {60, 125} },
    { name = "Neutral Seconds", range = {125, 175} },
    --{ name = "Equable heptatonic", range = {170, 180} },
    { name = "Major Seconds", range = {175, 240} },
    { name = "Interseptimal (Maj2-min3)", range = {240, 260} },
    { name = "Minor Thirds", range = {260, 330} },
    { name = "Neutral Thirds", range = {330, 372} },
    { name = "Major Thirds", range = {372, 440} },
    { name = "Interseptimal (Maj3-4)", range = {440, 468} },
    { name = "Perfect Fourths", range = {468, 528} },
    { name = "Superfourths", range = {528, 560} },
    { name = "Tritonic Region", range = {560, 640} },
    { name = "Subfifths", range = {640, 672} },
    { name = "Perfect Fifths", range = {672, 732} },
    { name = "Interseptimal (5-min6)", range = {732, 760} },
    { name = "Minor Sixths", range = {760, 828} },
    { name = "Neutral Sixths", range = {828, 870} },
    { name = "Major Sixths", range = {870, 940} },
    { name = "Interseptimal (Maj6-min7)", range = {940, 960} },
    { name = "Minor Sevenths", range = {960, 1030} },
    --{ name = "Equable heptatonic", range = {1025, 1030} },
    { name = "Neutral Sevenths", range = {1030, 1075} },
    { name = "Major Sevenths", range = {1075, 1140} },
    { name = "Octave less diesis", range = {1140, 1170} },
    { name = "Octave less comma", range = {1170, 1200} },
    { name = "Pure Octave (2:1)", range = {1200, 1200} }
}
-- EXPERIMENTAL FEATURE: interval lookup function
function lookup_interval_range(cents)
    for _, interval in ipairs(interval_ranges) do
        if cents >= interval.range[1] and cents <= interval.range[2] then
            return interval.name
        end
    end
    return "Out of range"
end


-- Main function; to be called by wrapper
-- Main function; to be called by wrapper

Revision as of 01:26, 6 October 2025

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
55 _mos_intervals (main) (args)
147 mos_intervals (invokable) (frame)
Lua modules required (5)
Variable Module Functions used
getArgs Module:Arguments getArgs
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
rat Module:Rational dependency not used
tamnams Module:TAMNAMS interval_quality
verify_prefix
verify_abbrev
yesno Module:Yesno yesno

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


local p = {}

local getArgs = require("Module:Arguments").getArgs
local mos = require("Module:MOS")
local rat = require("Module:Rational")
local tamnams = require("Module:TAMNAMS")
local yesno = require("Module:Yesno")

-- EXPERIMENTAL FEATURE: lookup table for intervals
-- Mostly based off Margo Schulter's categories (without large/medium/small),
-- but other interpretations are possible. Plus, this only goes up to 1200c.
interval_ranges = {
    { name = "Pure Unison (1:1)", range = {0, 0} },
    { name = "Commas", range = {0, 30} },
    { name = "Dieses", range = {30, 60} },
    { name = "Minor Seconds", range = {60, 125} },
    { name = "Neutral Seconds", range = {125, 175} },
    --{ name = "Equable heptatonic", range = {170, 180} },
    { name = "Major Seconds", range = {175, 240} },
    { name = "Interseptimal (Maj2-min3)", range = {240, 260} },
    { name = "Minor Thirds", range = {260, 330} },
    { name = "Neutral Thirds", range = {330, 372} },
    { name = "Major Thirds", range = {372, 440} },
    { name = "Interseptimal (Maj3-4)", range = {440, 468} },
    { name = "Perfect Fourths", range = {468, 528} },
    { name = "Superfourths", range = {528, 560} },
    { name = "Tritonic Region", range = {560, 640} },
    { name = "Subfifths", range = {640, 672} },
    { name = "Perfect Fifths", range = {672, 732} },
    { name = "Interseptimal (5-min6)", range = {732, 760} },
    { name = "Minor Sixths", range = {760, 828} },
    { name = "Neutral Sixths", range = {828, 870} },
    { name = "Major Sixths", range = {870, 940} },
    { name = "Interseptimal (Maj6-min7)", range = {940, 960} },
    { name = "Minor Sevenths", range = {960, 1030} },
    --{ name = "Equable heptatonic", range = {1025, 1030} },
    { name = "Neutral Sevenths", range = {1030, 1075} },
    { name = "Major Sevenths", range = {1075, 1140} },
    { name = "Octave less diesis", range = {1140, 1170} },
    { name = "Octave less comma", range = {1170, 1200} },
    { name = "Pure Octave (2:1)", range = {1200, 1200} }
}

-- EXPERIMENTAL FEATURE: interval lookup function
function lookup_interval_range(cents)
    for _, interval in ipairs(interval_ranges) do
        if cents >= interval.range[1] and cents <= interval.range[2] then
            return interval.name
        end
    end
    return "Out of range"
end

-- Main function; to be called by wrapper
function p._mos_intervals(args)
	-- Default param for input mos is 5L 2s
	local input_mos    = args["Input MOS"   ] or mos.new(5, 2, 2)
	local mos_prefix   = args["MOS Prefix"  ] or "mos"
	local mos_abbrev   = args["MOS Abbrev"  ] or "m"
	local is_collapsed = args["Is Collapsed"] == true
	
	-- 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)
	
	-- Create the table
	local result = '{| class="wikitable mw-collapsible' .. (is_collapsed and ' mw-collapsed"\n' or '"\n')
	
	-- Create table title
	result = result
		.. '|+ style="font-size: 105%; white-space: nowrap;" | ' .. string.format('Intervals of %s', scale_sig) .. '\n'
		.. '|-\n'
		
	-- Create table headers
	result = result
		.. '! colspan="3" | Intervals\n'
		.. '! rowspan="2" | Steps<br />subtended\n'
		.. '! rowspan="2" | Range in cents\n'
		.. '|-\n'	-- Start of second row of header cells
		.. '! Generic\n'
		.. '! Specific\n'
		.. '! Abbrev.\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 = string.format("%.1f{{c}}", mos.interval_to_cents(current_bright_interval, input_mos, {1, 1}))
			
			result = result
				.. "|-\n"
				.. "| '''" .. i-1 .. "-" .. mos_prefix .. "step'''\n"
				.. "| " .. tamnams.interval_quality(current_bright_interval, input_mos, "sentence-case", mos_prefix) .. "\n"
				.. "| " .. tamnams.interval_quality(current_bright_interval, input_mos, "abbrev"       , mos_abbrev) .. "\n"
				.. "| <span style=\"white-space: nowrap;\">" .. mos.interval_as_string(current_bright_interval) .. "</span>\n"
				.. "| " .. cents .. "\n"
		else
			-- 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{{c}} to %.1f{{c}}", math.min(sm_min_cents, sm_max_cents), math.max(sm_min_cents, sm_max_cents))
			local bright_interval_range = string.format("%.1f{{c}} to %.1f{{c}}", math.min(lg_min_cents, lg_max_cents), math.max(lg_min_cents, lg_max_cents))
	
			result = result
			.. "|-\n"
			.. '| rowspan="2" | ' .. i-1 .. '-' .. mos_prefix .. 'step\n'
			.. "| " .. tamnams.interval_quality(current_dark_interval, input_mos, "sentence-case", mos_prefix) .. "\n"
			.. "| " .. tamnams.interval_quality(current_dark_interval, input_mos, "abbrev"       , mos_abbrev) .. "\n"
			.. "| <span style=\"white-space: nowrap;\">" .. mos.interval_as_string(current_dark_interval) .. "</span>\n"
			.. "| " .. dark_interval_range .. "\n"
			.. "|-\n"
			.. "| " .. tamnams.interval_quality(current_bright_interval, input_mos, "sentence-case", mos_prefix) .. "\n"
			.. "| " .. tamnams.interval_quality(current_bright_interval, input_mos, "abbrev"       , mos_abbrev) .. "\n"
			.. "| <span style=\"white-space: nowrap;\">" .. mos.interval_as_string(current_bright_interval) .. "</span>\n"
			.. "| " .. bright_interval_range .. "\n"
		end
	end
	result = result .. "|}"

	return result
end

-- Wrapper function; to be called by template
function p.mos_intervals(frame)
	local args = getArgs(frame)
	
	-- Preprocess scalesig into input mos
	local input_mos = mos.parse(args["Scale Signature"])
	args["Input MOS"] = input_mos
	args["Scale Signature"] = nil

	-- Preprocess collapse option
	args["Collapsed"] = yesno(args["Collapsed"], false)
	
	-- Preprocess (verify) prefix/abbrev
	args["MOS Prefix"] = tamnams.verify_prefix(input_mos, args["MOS Prefix"])
	args["MOS Abbrev"] = tamnams.verify_abbrev(input_mos, args["MOS Abbrev"])

	local result = p._mos_intervals(args)
	local debugg = yesno(args["debug"])
	
	-- Debugger option
	if debugg == true then
		result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
	end
	
	return frame:preprocess(result)
end

return p