Module:MOS mode degrees: Difference between revisions
Jump to navigation
Jump to search
Added cell coloring to indicate which cells are perfect intervals (default color), the large size (light yellow), the small size (light blue), or an altered size (light red) |
m Row coloring for perfect intervals now only applies to periods |
||
| Line 526: | Line 526: | ||
local darkest_vector = p.calculate_mode_degrees(input_mos, darkest_true_mode) | local darkest_vector = p.calculate_mode_degrees(input_mos, darkest_true_mode) | ||
local cell_color_perfect_size = "NONE" | local cell_color_perfect_size = "NONE" -- Only applies for periods, including the root and equave | ||
local cell_color_large_size = "fffff0" | local cell_color_large_size = "fffff0" | ||
local cell_color_small_size = "eaeaff" | local cell_color_small_size = "eaeaff" | ||
| Line 535: | Line 535: | ||
local cell_colors = {} | local cell_colors = {} | ||
for j = 1, #input_mode_vectors[i] do | for j = 1, #input_mode_vectors[i] do | ||
if input_mode_vectors[i][j] == | if input_mode_vectors[i][j] == brightest_vector[j] and input_mode_vectors[i][j] == darkest_vector[j] then | ||
table.insert(cell_colors, cell_color_perfect_size) | table.insert(cell_colors, cell_color_perfect_size) | ||
elseif brightest_vector[j] == input_mode_vectors[i][j] then | elseif brightest_vector[j] == input_mode_vectors[i][j] then | ||
Revision as of 06:31, 20 November 2023
- This module should not be invoked directly; use its corresponding template instead: Template:MOS mode degrees.
This module creates a table of the scale degrees for each mode of a MOS or MODMOS scale.
| Introspection summary for Module:MOS mode degrees | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
No function descriptions were provided. The Lua code may have further information.
local mos = require('Module:MOS')
local rat = require('Module:Rational')
local utils = require('Module:Utils')
local mosnot = require('Module:MOS notation')
local et = require('Module:ET')
local p = {}
-- TODO?: Move some functions to the mos notation module
-- Helper function
-- Parses entries from a semicolon-delimited string and returns them in an array
-- TODO: Separate this and related functions (parse_pair and parse_kv_pairs) into its own module, as they're included
-- in various modules at this point, such as: scale tree, mos mdoes
function p.parse_entries(unparsed)
local parsed = {}
for entry in string.gmatch(unparsed, '([^;]+)') do
local trimmed = entry:gsub("^%s*(.-)%s*$", "%1")
table.insert(parsed, trimmed) -- Add to array
end
return parsed
end
-- Helper function
-- Extracts the prefix from the mos module, without the dash and without any text after that
-- May need to revisit to clean up code since this splits text at the "-".
function p.get_mos_prefix(scale_sig)
local unparsed = mos.tamnams_prefix[scale_sig]
local parsed = {}
if unparsed == nil then
return "mos"
else
for entry in string.gmatch(unparsed, '([^-]+)') do
local trimmed = entry:gsub("^%s*(.-)%s*$", "%1")
table.insert(parsed, trimmed) -- Add to array
end
return parsed[1]
end
end
-- Helper function
-- Determines whether an item is in an array
function p.find_item_in_table(table, item)
local item_found = false
for i = 1, #table do
if table[i] == item then
item_found = true
break
end
end
return item_found
end
-- Calculate the mosstep vector from the mosstep pattern
-- Given a mos's step pattern and a quantity of mossteps, this takes an interval
-- (as a substring of the mosstep pattern starting from the root) and counts the number
-- of L's and s's in that substring. Allowed steps are:
-- - L - a single large step
-- - s - a single small step (can be capital S or lowercase s)
-- - c - a single chroma, defined as L-s; counting this adds one L and subtracts one s
-- Note that L-c=s and s+c=L.
-- - A - an augmented step, defined as L+c; counting this adds two L's and subtracts one s
-- - d - a diminished step, defined as s-c; counting this subtracts one L and adds two s's
-- Note that adding a chroma to an s makes it an L, and removing a chroma from an L
-- makes it an s.
-- Above-equave mossteps are supported.
function p.convert_mosstep_pattern_to_mosstep_vector(mosstep_pattern, mossteps)
local mossteps = mossteps or 7
local mosstep_pattern = mosstep_pattern or "LLLsLLs"
local large_step_count = 0
local small_step_count = 0
local step_count = #mosstep_pattern
-- If the number of mossteps exceeds the mosstep pattern, divide that quantity
-- by the number of steps and round up, then duplicate the pattern by that much.
local number_of_repetitions = math.ceil(mossteps / step_count)
local mosstep_pattern_duplicated = string.rep(mosstep_pattern, number_of_repetitions)
-- Count the number of L's and s's in the string
-- C's, A's, and d's are worth some number of L's and s's
for i = 1, mossteps do
local step = string.sub(mosstep_pattern_duplicated, i, i)
if step == "L" then
large_step_count = large_step_count + 1
elseif step == "s" or step == "S" then
small_step_count = small_step_count + 1
elseif step == "c" then
large_step_count = large_step_count + 1
small_step_count = small_step_count - 1
elseif step == "A" then
large_step_count = large_step_count + 2
small_step_count = small_step_count - 1
elseif step == "d" then
large_step_count = large_step_count - 1
small_step_count = small_step_count + 2
end
end
local mosstep_vector = { ['L'] = large_step_count, ['s'] = small_step_count }
return mosstep_vector
end
-- Produce an encoded mosdegree from a mosstep vector
-- For an interval with two specific sizes, its large size is iL js and small size
-- is (i-1)L (j+1)s, for a difference of a single large step swapped with a single
-- small step. Alterations are denoted by adding or subtracting chromas, c, where a
-- chroma is L-s. An augmented step is L+c, denoted with a single A, so A=L+L-s.
-- Summing the number of L's and s's cancels out any negative step quantites for the
-- single A, so for any mosstep represented as a string of L's, s's, and A's, the sum of
-- the number of L's and s's produces the original interval (in mossteps). Similar
-- inductive reasoning applies with c's and d's, should a scale contain such steps.
-- To find how many alterations a mosstep vector had been applied to it:
-- - First, add or subtract chromas as needed until neither the L-count nor s-count
-- is negative, and record that number of chromas as k1.
-- - Then compare the step vector of that mosstep with the expected large or small size.
-- However many chromas are needed to add to reach the small size, or how many
-- chromas are need to remove to reach the small size, is the additional number
-- of chromas, k2, needed to reach the mos's large or small size. (Note that if
-- adding chromas, k2 is positive, but if removing chromas, k2 is negative.)
-- - Add k1 and k2. This is the number of alrerations the mosstep was from its large
-- or small size.
-- - Note that for mossteps with two specific sizes, there are effectively two "zero
-- points", one each for the large and small size, and which one to use depends
-- on whichever is closer. For mossteps with only one size, there is only one
-- zero point.
function p.calculate_mosstep_quality(input_mos, mosstep_vector)
local input_mos = input_mos or mos.new(5, 2)
local mosstep_vector = mosstep_vector or { ['L'] = 5, ['s'] = 2 }
-- Get the number of mossteps per period and equave, and periods per equave
local mossteps_per_equave = (input_mos.nL + input_mos.ns)
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the number of mossteps in the bright gen and dark gen
local bright_gen = mos.bright_gen(input_mos)
local mossteps_per_bright_gen = bright_gen['L'] + bright_gen['s']
local mossteps_per_dark_gen = mossteps_per_period - mossteps_per_bright_gen
-- Get the number of mossteps as the sum of the number of L's and s's
local mossteps = mosstep_vector['L'] + mosstep_vector['s']
-- Get the brightest and darkest modes for the input mos
local brightest_mode = mos.brightest_mode(input_mos)
local darkest_mode = string.reverse(brightest_mode)
-- Get the expected vector of the large mosstep
local expected_large_mosstep_vector = p.convert_mosstep_pattern_to_mosstep_vector(brightest_mode, mossteps)
-- Since the size difference between the large and small intervals is a single chroma,
-- which is L-s, simply count the large step difference between the given mosstep vector
-- and the expected large and small ones.
local large_step_count = mosstep_vector['L']
local number_of_chromas = large_step_count - expected_large_mosstep_vector['L']
-- Determine what mosstep was passed in; is it a generator or period?
local mos_is_nL_ns = input_mos.nL == input_mos.ns
local mosstep_is_period = mossteps % mossteps_per_period == 0
local mosstep_is_bright_gen = mossteps % mossteps_per_period == mossteps_per_bright_gen and not mos_is_nL_ns
local mosstep_is_dark_gen = mossteps % mossteps_per_period == mossteps_per_dark_gen and not mos_is_nL_ns
-- Rules for encoding are shown in the comments below.
-- Encoding follows the rules as found in the module mos notation, where:
-- - 3 = 2x augmented
-- - 2 = 1x augmented
-- - 1 = major
-- - 0 = perfect (used for generators, roots, and periods)
-- - -1 = minor
-- - -2 = 1x diminished
-- - -3 = 2x diminished
-- The number_of_chromas found previously must be translated to this encoding.
local encoded_quality = 0
if mosstep_is_period then
-- For periods:
-- - If the number of chromas is 1 or more, that quality is augmented
-- - If the nubmer of chromas is 0, that quality is perfect
-- - If the number of chromas is -1 or less, that quality is diminished
-- The encoded quality should always skip 1 and -1
if number_of_chromas >= 1 then
encoded_quality = number_of_chromas + 1
elseif number_of_chromas == 0 then
encoded_quality = number_of_chromas
elseif number_of_chromas <= -1 then
encoded_quality = number_of_chromas - 1
end
elseif mosstep_is_bright_gen then
-- For bright gens:
-- If the number of chromas is 1 or more, that quality is augmented
-- If the number of chromas is 0, that quality is perfect
-- If the number of chromas is -1 or less, that is diminished
-- The encoded quality should always skip 1 and -1
if number_of_chromas >= 1 then
encoded_quality = number_of_chromas + 1
elseif number_of_chromas == 0 then
encoded_quality = 0
elseif number_of_chromas <= -1 then
encoded_quality = number_of_chromas - 1
end
elseif mosstep_is_dark_gen then
-- For bright gens:
-- If the number of chromas is 0 or more, that quality is augmented
-- If the number of chromas is -1, that quality is perfect
-- If the number of chromas is -2 or less, that is diminished
-- The encoded quality should always skip 1 and -1
if number_of_chromas >= 0 then
encoded_quality = number_of_chromas + 2
elseif number_of_chromas == -1 then
encoded_quality = 0
elseif number_of_chromas <= -2 then
encoded_quality = number_of_chromas
end
else
-- For all other intervals:
-- If the number of chromas is 1 or more, that quality is augmented
-- If the number of chromas is 0, that quality is major
-- If the number of chromas is -1, that quality is minor
-- If the number of chromas is -2 or less, that is diminished
-- The encoded quality should always skip 0
if number_of_chromas >= 1 then
encoded_quality = number_of_chromas + 1
elseif number_of_chromas == 0 then
encoded_quality = 1
elseif number_of_chromas == -1 then
encoded_quality = -1
elseif number_of_chromas <= -2 then
encoded_quality = number_of_chromas
end
end
return encoded_quality
end
-- Helper function
-- Given a quality (and only a quality), decode it from a numeric value to text.
-- Encoding follows the rules as found in the module mos notation, where:
-- - 3 = 2x augmented
-- - 2 = 1x augmented
-- - 1 = major
-- - 0 = perfect (used for generators, roots, and periods)
-- - -1 = minor
-- - -2 = 1x diminished
-- - -3 = 2x diminished
-- That encoded value should be converted back to text
function p.decode_quality(encoded_quality)
local quality_as_text = ""
if encoded_quality == 0 then
quality_as_text = "Perf."
elseif encoded_quality == 1 then
quality_as_text = "Maj."
elseif encoded_quality == 2 then
quality_as_text = "Aug."
elseif encoded_quality > 2 then
quality_as_text = (encoded_quality - 1) .. "× Aug."
elseif encoded_quality == -1 then
quality_as_text = "Min."
elseif encoded_quality == -2 then
quality_as_text = "Dim."
elseif encoded_quality < -2 then
quality_as_text = (math.abs(encoded_quality) - 1) .. "× Dim."
end
return quality_as_text
end
-- Helper function
-- Calcualtes the qualities of each scale degree given a mosstep pattern and input mos
-- Input mos is necessary for comparing step patterns with the true mos pattern, esp. with modmosses.
function p.calculate_mode_degrees(input_mos, mosstep_pattern)
local input_mos = input_mos or mos.new(5, 2)
local mosstep_pattern = mosstep_pattern or "LLsLLLs"
-- Get the number of mossteps per period
local mossteps_per_equave = input_mos.nL + input_mos.ns
local mode_degrees = {}
for i = 1, mossteps_per_equave + 1 do
local mossteps = i - 1
local mosdegree_vector = p.convert_mosstep_pattern_to_mosstep_vector(mosstep_pattern, mossteps)
local encoded_mosdegree = p.calculate_mosstep_quality(input_mos, mosdegree_vector)
table.insert(mode_degrees, encoded_mosdegree)
end
return mode_degrees
end
-- Helper function
-- Calculate the UDP for each mode, given the modes are for the true
-- mos pattern and start at the brightest mode
function p.calculate_mos_mode_brightness_order(input_mos)
local input_mos = input_mos or mos.new(5, 2)
-- Get the number of mossteps per period and equave, and periods per equave
local mossteps_per_equave = (input_mos.nL + input_mos.ns)
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the number of mossteps in the bright gen and dark gen
local bright_gen = mos.bright_gen(input_mos)
local mossteps_per_bright_gen = bright_gen['L'] + bright_gen['s']
-- For each scale degree within a single period of a step pattern,
-- there is a unique mode.
-- If the mos is single-period, then there are x+y unique modes.
-- If the mos is multi-period nxL nys, then there are x+y modes instead
-- of nx+ny modes due to repetition.
local number_of_modes = mossteps_per_period
local number_of_gens_down = 0
local number_of_gens_up = mossteps_per_equave - periods_per_equave
local brightness_order = {}
for i = 1, mossteps_per_period do
local current_mode_brightness = number_of_gens_up .. '|' .. number_of_gens_down
table.insert(brightness_order, current_mode_brightness)
number_of_gens_up = number_of_gens_up - periods_per_equave
number_of_gens_down = number_of_gens_down + periods_per_equave
end
return brightness_order
end
-- Helper function
-- Calculate the rotational order for each mode, given the modes are
-- for the true mos pattern and start at the brightest mode
function p.calculate_mos_mode_rotational_order(input_mos)
local input_mos = input_mos or mos.new(5, 2)
-- Get the number of mossteps per period and equave, and periods per equave
local mossteps_per_equave = (input_mos.nL + input_mos.ns)
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the number of mossteps in the bright gen and dark gen
local bright_gen = mos.bright_gen(input_mos)
local mossteps_per_bright_gen = bright_gen['L'] + bright_gen['s']
-- For each scale degree within a single period of a step pattern,
-- there is a unique mode.
-- If the mos is single-period, then there are x+y unique modes.
-- If the mos is multi-period nxL nys, then there are x+y modes instead
-- of nx+ny modes due to repetition.
local number_of_modes = mossteps_per_period
local bright_gens_up = 0
local rotational_order = {}
for i = 1, mossteps_per_period do
local current_mode_order = bright_gens_up % mossteps_per_period + 1
bright_gens_up = bright_gens_up + mossteps_per_bright_gen
table.insert(rotational_order, current_mode_order)
end
return rotational_order
end
-- Calculate the rotations of a step pattern
-- Modes can either be sorted by decreasing brightness or by leftward shifts (rotation)
-- This is meant to be used as a helper function for the following functions:
-- - calculate_mos_mode_degrees
-- - calculate_modmos_mode_degrees
function p.calculate_step_pattern_rotations(input_mos, mosstep_pattern, rotate_by_generator)
local input_mos = input_mos or mos.new(6, 4)
local mosstep_pattern = mosstep_pattern or "LLsLsLLsLs"
local rotate_by_generator = rotate_by_generator == true
-- Get the number of mossteps per period and equave, and periods per equave
local mossteps_per_equave = (input_mos.nL + input_mos.ns)
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the amount to shift by
-- Shifting by the number of mossteps in the generator produces modes by
-- descending brightness; shifting by 1 produces them in rotational order
local shift_amount = 1
if rotate_by_generator then
local bright_gen = mos.bright_gen(input_mos)
local mossteps_per_bright_gen = bright_gen['L'] + bright_gen['s']
shift_amount = mossteps_per_bright_gen
end
local current_mode = mosstep_pattern
local rotations = {}
for i = 1, mossteps_per_equave do
if not p.find_item_in_table(rotations, current_mode) then
table.insert(rotations, current_mode)
end
-- Rotate current mode
local first_substr = string.sub(current_mode, 1, shift_amount)
local second_substr = string.sub(current_mode, shift_amount + 1, mossteps_per_equave)
current_mode = second_substr .. first_substr
end
return rotations
end
-- Helper function
-- Calculate the scale degrees given an input mos and its modes
-- Modes can also be modmos modes
function p.calculate_mos_mode_degrees(input_mos, modes)
local input_mos = input_mos or mos.new(5, 2)
local modes = modes or p.calculate_step_pattern_rotations(input_mos, "LLLsLLs", true)
-- Get the number of mossteps per period and equave
local mossteps_per_equave = input_mos.nL + input_mos.ns
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the number of mossteps per bright gen
local bright_gen = mos.bright_gen(input_mos)
local mossteps_per_bright_gen = bright_gen['L'] + bright_gen['s']
local mode_degrees = {}
for i = 1, #modes do
local current_mode_degrees = p.calculate_mode_degrees(input_mos, modes[i])
table.insert(mode_degrees, current_mode_degrees)
end
return mode_degrees
end
-- Helper function
-- For a given modmos step pattern, find the closest true-mos mode and alterations
-- The mos and its modes in rotational order should also be passed in
-- This finds the closest mode for only the modmos's step pattern, not all rotations
-- Alterations are denoted as a UDP followed by which scale degrees are altered from the original mode
-- If multiple true-mos modes are tied with being closest, use the darkest mode instead
function p.compare_modmos_with_true_mos_modes(input_mos, true_mos_modes, modmos_step_pattern)
local input_mos = input_mos or mos.new(5, 2)
local true_mos_modes = true_mos_modes or p.calculate_step_pattern_rotations(mos.new(5, 2), "LLLsLLs", true)
local modmos_step_pattern = modmos_step_pattern or "sLsLLsA"
-- Get the number of mossteps per period and equave, and periods per equave
local mossteps_per_equave = (input_mos.nL + input_mos.ns)
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the modmos's mosstep vectors to compare
local modmos_vector = p.calculate_mode_degrees(input_mos, modmos_step_pattern)
-- Compare each mode in the array of mos modes.
-- For each mode compared, count how many alterations there are. The mode
-- with the fewest alterations is the closest true mos mode.
local index_of_closest_mode = 1
local lowest_number_of_alterations = mossteps_per_equave
for i = 1, #true_mos_modes do
local number_of_alterations = 0
-- Get the current mode's degree vector
local mode_vector = p.calculate_mode_degrees(input_mos, true_mos_modes[i])
-- Compare the vectors
for j = 1, #true_mos_modes[i] do
if mode_vector[j] ~= modmos_vector[j] then
number_of_alterations = number_of_alterations + 1
end
end
-- If the current mode had fewer alterations, update
if number_of_alterations < lowest_number_of_alterations then
index_of_closest_mode = i
lowest_number_of_alterations = number_of_alterations
end
-- If the current mode had the same number of alterations but is of a darker mode, update
--if number_of_alterations == lowest_number_of_alterations then
-- index_of_closest_mode = i
--end
end
-- Calculate the UDP of the closest mode
local gens_down = (index_of_closest_mode - 1) * periods_per_equave
local gens_up = (mossteps_per_period - index_of_closest_mode) * periods_per_equave
local udp_of_closest_mode = gens_up .. '|' .. gens_down
-- Calculate alterations by comparing the modmos and the closest mode's degrees
local mode_vector = p.calculate_mode_degrees(input_mos, true_mos_modes[index_of_closest_mode])
local alterations = ""
for i = 1, #mode_vector do
if mode_vector[i] ~= modmos_vector[i] then
local encoded_degree = { ['Mossteps'] = i - 1, ['Quality'] = modmos_vector[i] }
local decoded_degree = mosnot.decode_mosstep_quality(encoded_degree, "m", "mosdegree", "abbreviated")
alterations = string.format("%s %s", alterations, decoded_degree)
end
end
return udp_of_closest_mode .. alterations
end
-- Helper function
-- For a given modmos step pattern, find the closest true-mos mode and alterations for each modmos mode
function p.calculate_modmos_mode_alterations(input_mos, modmos_step_pattern)
local input_mos = input_mos or mos.new(5, 2)
local modmos_step_pattern = modmos_step_pattern or "LLLsLLs"
-- Calculate the modes for the truemos and modmos
local true_mos_modes = p.calculate_step_pattern_rotations(input_mos, mos.brightest_mode(input_mos), true)
local modmos_modes = p.calculate_step_pattern_rotations(input_mos, modmos_step_pattern, false)
-- Get each mode's alterations
local alterations = {}
for i = 1, #modmos_modes do
local alteration = p.compare_modmos_with_true_mos_modes(input_mos, true_mos_modes, modmos_modes[i])
table.insert(alterations, alteration)
end
return alterations
end
-- Helper function
-- Calculates row colors given precalculated degree vectors for each mode
function p.calculate_row_colors(input_mos, input_mode_vectors)
-- Default input mos and brightest/darkest true mos modes
local input_mos = input_mos or mos.new(5, 2)
local brightest_true_mode = mos.brightest_mode(input_mos)
local darkest_true_mode = string.reverse(brightest_true_mode)
-- Default input mode vectors
local input_mode_vectors = input_mode_vectors or p.calculate_mos_mode_degrees(input_mos, p.calculate_step_pattern_rotations(input_mos, brightest_true_mode, true))
-- Brightest and darkest vectors
local brightest_vector = p.calculate_mode_degrees(input_mos, brightest_true_mode)
local darkest_vector = p.calculate_mode_degrees(input_mos, darkest_true_mode)
local cell_color_perfect_size = "NONE" -- Only applies for periods, including the root and equave
local cell_color_large_size = "fffff0"
local cell_color_small_size = "eaeaff"
local cell_color_altered_size = "ffe0e0"
local row_colors = {}
for i = 1, #input_mode_vectors do
local cell_colors = {}
for j = 1, #input_mode_vectors[i] do
if input_mode_vectors[i][j] == brightest_vector[j] and input_mode_vectors[i][j] == darkest_vector[j] then
table.insert(cell_colors, cell_color_perfect_size)
elseif brightest_vector[j] == input_mode_vectors[i][j] then
table.insert(cell_colors, cell_color_large_size)
elseif darkest_vector[j] == input_mode_vectors[i][j] then
table.insert(cell_colors, cell_color_small_size)
else
table.insert(cell_colors, cell_color_altered_size)
end
end
table.insert(row_colors, cell_colors)
end
return row_colors
end
-- Create a table of a mos's degrees
function p.mos_mode_degrees(input_mos, mos_prefix, mode_names)
local input_mos = input_mos or mos.new(5, 2)
local mos_prefix = mos_prefix or "mos"
local mode_names = mode_names or nil
-- Get the modes
local input_mode = mos.brightest_mode(input_mos)
local modes = p.calculate_step_pattern_rotations(input_mos, input_mode, true)
-- Get the number of mossteps per period and equave, and periods per equave
local mossteps_per_equave = (input_mos.nL + input_mos.ns)
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the scale sig
local scale_sig = mos.as_string(input_mos)
-- Get the brightness and rotational orderings
local brightness_order = p.calculate_mos_mode_brightness_order(input_mos)
local rotational_order = p.calculate_mos_mode_rotational_order(input_mos)
-- Get mosstep vectors for all modes
local mosstep_vectors = p.calculate_mos_mode_degrees(input_mos, modes)
-- Get row colors
local row_colors = p.calculate_row_colors(input_mos, mosstep_vectors)
-- Create table
local result = '{| class="wikitable sortable"\n'
local result = result .. string.format("|+ Scale degrees of %s modes\n", scale_sig)
-- Add table headers for first row
result = result .. '! rowspan="2" | UDP\n'
result = result .. '! rowspan="2" | Rotational Order\n'
result = result .. '! rowspan="2" | Step pattern\n'
-- Add mode names if present
local mode_names_given = mode_names ~= nil and #mode_names == #modes
if mode_names_given then
result = result .. '! rowspan="2" class="unsortable" | Mode names\n'
end
-- Add header for scale degrees
result = result .. string.format('! colspan="%d" class="unsortable" | Scale degree (%sdegree)\n', mossteps_per_equave + 1, mos_prefix)
-- Add second row of headers
result = result .. "|-\n"
for i = 1, mossteps_per_equave + 1 do
result = result .. string.format('! class="unsortable" |%d\n', i-1)
end
-- Add table contents
for i = 1, #modes do
result = result .. "|-\n"
-- Add brightness order (as UDP), rotational order, and step pattern
result = result .. string.format('| %s\n| %s\n| %s\n', brightness_order[i], rotational_order[i], modes[i])
-- Add mode name if given
if mode_names_given then
result = result .. string.format('| %s\n', mode_names[i])
end
-- Add scale degrees with cell coloring
for j = 1, #mosstep_vectors[i] do
if row_colors[i][j] == "NONE" then
result = result .. string.format('| %s\n', p.decode_quality(mosstep_vectors[i][j]))
else
result = result .. string.format('| style="background:#%s" | %s\n', row_colors[i][j], p.decode_quality(mosstep_vectors[i][j]))
end
end
end
-- End of table
result = result .. "|}"
return result
end
-- Create a table of a modmos's degrees
function p.modmos_mode_degrees(input_mos, mos_prefix, step_pattern, mode_names)
local input_mos = input_mos or mos.new(5, 2)
local mos_prefix = mos_prefix or "mos"
local step_pattern = step_pattern or "LsLLsAs"
local mode_names = mode_names or nil
-- Get the modes
local modes = p.calculate_step_pattern_rotations(input_mos, step_pattern, false)
-- Get the number of mossteps per period and equave, and periods per equave
local mossteps_per_equave = (input_mos.nL + input_mos.ns)
local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns)
local mossteps_per_period = mossteps_per_equave / periods_per_equave
-- Get the scale sig
local scale_sig = mos.as_string(input_mos)
-- Get rotational orderings; modmossses shouldn't be ordered by brightness
local rotational_order = p.calculate_mos_mode_rotational_order(input_mos)
-- Get closest mode and alterations
local alterations = p.calculate_modmos_mode_alterations(input_mos, step_pattern)
-- Get mosstep vectors for all modes
local mosstep_vectors = p.calculate_mos_mode_degrees(input_mos, modes)
-- Get row colors
local row_colors = p.calculate_row_colors(input_mos, mosstep_vectors)
-- Create table
local result = '{| class="wikitable sortable"\n'
local result = result .. string.format("|+ Scale degrees of %s modes (step pattern of %s)\n", scale_sig, step_pattern)
-- Add table headers for first row
result = result .. '! rowspan="2" | UDP and alterations\n'
result = result .. '! rowspan="2" | Rotational Order\n'
result = result .. '! rowspan="2" | Step pattern\n'
-- Add mode names if present
local mode_names_given = mode_names ~= nil and #mode_names == #modes
if mode_names_given then
result = result .. '! rowspan="2" class="unsortable" | Mode names\n'
end
-- Add header for scale degrees
result = result .. string.format('! colspan="%d" class="unsortable" | Scale degree (%sdegree)\n', mossteps_per_equave + 1, mos_prefix)
-- Add second row of headers
result = result .. "|-\n"
for i = 1, mossteps_per_equave + 1 do
result = result .. string.format('! class="unsortable" |%d\n', i-1)
end
-- Add table contents
for i = 1, #modes do
result = result .. "|-\n"
-- Add brightness order (as UDP), rotational order, and step pattern
result = result .. string.format('| %s\n| %s\n| %s\n', alterations[i], i, modes[i])
-- Add mode name if given
if mode_names_given then
result = result .. string.format('| %s\n', mode_names[i])
end
-- Add scale degrees with cell coloring
for j = 1, #mosstep_vectors[i] do
if row_colors[i][j] == "NONE" then
result = result .. string.format('| %s\n', p.decode_quality(mosstep_vectors[i][j]))
else
result = result .. string.format('| style="background:#%s" | %s\n', row_colors[i][j], p.decode_quality(mosstep_vectors[i][j]))
end
end
end
-- End of table
result = result .. "|}"
return result
end
-- Function to be called as part of a template
function p.mos_mode_degrees_frame(frame)
-- Default param for input mos is 5L 2s
local input_mos = mos.parse(frame.args['Scale Signature']) or mos.new(2, 5, 2)
-- Get the scale sig; for calculating the mos prefix
local scale_sig = mos.as_string(input_mos)
-- 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 mos_prefix = "mos"
if frame.args['MOS Prefix'] == "NONE" then
mos_prefix = ""
elseif string.len(frame.args['MOS Prefix']) == 0 then
mos_prefix_lookup = p.get_mos_prefix(scale_sig)
if string.len(mos_prefix_lookup) ~= 0 then
mos_prefix = mos_prefix_lookup
end
else
mos_prefix = frame.args['MOS Prefix']
end
-- Get the step pattern
local step_pattern = frame.args['MODMOS Step Pattern']
-- Get the mode names
local mode_names = nil
-- Default names for 5L 2s modes
if scale_sig == "5L 2s" and step_pattern == "LsLLsAs" then
mode_names = { "Harmonic minor", "Locrian #6", "Ionian augmented", "Dorian #4", "Phrygian dominant", "Lydian #2", "Locrian b4 bb7" }
elseif scale_sig == "5L 2s" and #step_pattern == 0 then
mode_names = { "Lydian", "Ionian (major)", "Mixolydian", "Dorian", "Aeolian (minor)", "Phrygian", "Locrian" }
end
-- If mode names are given, use those instead
if #frame.args['Mode Names'] ~= 0 then
mode_names = p.parse_entries(frame.args['Mode Names'])
end
-- If a modmos step pattern was never provided, call the function mos_mode_degrees
-- Otherwise, call the function modmos_mode_degrees
local result = ""
if step_pattern == "" then
result = p.mos_mode_degrees(input_mos, mos_prefix, mode_names)
elseif #step_pattern == input_mos.nL + input_mos.ns then
result = p.modmos_mode_degrees(input_mos, mos_prefix, step_pattern, mode_names)
end
return result
end
return p