Module:MOS notation: Difference between revisions
Module now uses ordinal module |
ArrowHead294 (talk | contribs) m Sort dependencies |
||
| (11 intermediate revisions by 3 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
-- | local mos = require("Module:MOS") | ||
local ord = require("Module:Ordinal") | |||
local rat = require("Module:Rational") | |||
local utils = require("Module:Utils") | |||
-- ---------------------------------------------------------------------------- | |||
-- ------------------------ PARSER FUNCTIONS ---------------------------------- | |||
-- ---------------------------------------------------------------------------- | |||
-- Parser function | |||
-- Parses notation entered as a string; for example, | |||
-- "CDEFGAB; #; b" becomes an associative array, where: | -- "CDEFGAB; #; b" becomes an associative array, where: | ||
-- - the key ['Naturals'] has the value "CDEFGAB"; also called "nominals" | -- - the key ['Naturals'] has the value "CDEFGAB"; also called "nominals" | ||
| Line 18: | Line 22: | ||
local parsed = {} | local parsed = {} | ||
for entry in string.gmatch(unparsed, | for entry in string.gmatch(unparsed, "([^;]+)") do | ||
local trimmed = entry:gsub("^%s*(.-)%s*$", "%1") | local trimmed = entry:gsub("^%s*(.-)%s*$", "%1") | ||
table.insert(parsed, trimmed) -- Add to array | table.insert(parsed, trimmed) -- Add to array | ||
end | end | ||
local notation = { [ | local notation = { ["Naturals"] = parsed[1], ["Sharp"] = parsed[2], ["Flat"] = parsed[3] } | ||
if #parsed == 3 then | if #parsed == 3 then | ||
return notation | return notation | ||
| Line 31: | Line 35: | ||
end | end | ||
-- | -- Parser function | ||
-- Parses a step ratio entered as a string "p/q" | |||
function p.parse_step_ratio(unparsed) | function p.parse_step_ratio(unparsed) | ||
local parsed = {} | local parsed = {} | ||
for entry in string.gmatch(unparsed, | for entry in string.gmatch(unparsed, "([^/]+)") do | ||
local trimmed = entry:gsub("^%s*(.-)%s*$", "%1") | local trimmed = entry:gsub("^%s*(.-)%s*$", "%1") | ||
table.insert(parsed, trimmed) -- Add to array | table.insert(parsed, trimmed) -- Add to array | ||
| Line 47: | Line 52: | ||
end | end | ||
-- Helper function | -- Helper function | ||
-- Simplifies step ratio | |||
function p.simplify_step_ratio(step_ratio_unsimplified) | function p.simplify_step_ratio(step_ratio_unsimplified) | ||
local kp = step_ratio_unsimplified[1] | local kp = step_ratio_unsimplified[1] | ||
local kq = step_ratio_unsimplified[2] | local kq = step_ratio_unsimplified[2] | ||
local k = | local k = utils._gcd(kp, kq) | ||
local num = kp / k | local num = kp / k | ||
local den = kq / k | local den = kq / k | ||
| Line 59: | Line 65: | ||
end | end | ||
-- | -- Parser function | ||
-- Parses a UDP entered as a string "up,dp" | |||
-- To avoid potential issues, the "," character is used instead of "|" | -- To avoid potential issues, the "," character is used instead of "|" | ||
function p.parse_udp(step_ratio_unparsed) | function p.parse_udp(step_ratio_unparsed) | ||
local parsed = {} | local parsed = {} | ||
for entry in string.gmatch(step_ratio_unparsed, | for entry in string.gmatch(step_ratio_unparsed, "([^,]+)") do | ||
local trimmed = entry:gsub("^%s*(.-)%s*$", "%1") | local trimmed = entry:gsub("^%s*(.-)%s*$", "%1") | ||
table.insert(parsed, trimmed) -- Add to array | table.insert(parsed, trimmed) -- Add to array | ||
| Line 76: | Line 83: | ||
end | end | ||
-- | -- ---------------------------------------------------------------------------- | ||
-- ------------------------ DECODER FUNCTIONS --------------------------------- | |||
-- ---------------------------------------------------------------------------- | |||
-- Decoder function | |||
-- Decodes a note name given as a quantity of mossteps | |||
-- and chromas (see gamut function) into a name, such as "C#" | -- and chromas (see gamut function) into a name, such as "C#" | ||
-- To be used in conjunction with the genchain function | -- To be used in conjunction with the genchain function | ||
function p. | -- Function is currently not used in any other modules as of time 2023-10-21 | ||
function p.decode_note_name(mossteps, chromas, note_symbol, chroma_symbol) | |||
local note_name = note_symbol .. string.rep(chroma_symbol, math.abs(chromas)) | local note_name = note_symbol .. string.rep(chroma_symbol, math.abs(chromas)) | ||
| Line 85: | Line 98: | ||
end | end | ||
-- Helper function | -- Decoder function | ||
-- Decodes a scale degree given as a quantity of mossteps | |||
-- and a numeric quality (0=perf, 1=maj, -1=min, 2=aug, -2=dim, etc) into a | |||
-- scale degree | |||
-- To be used in conjunction with the degrees function | |||
-- For notation: options include mosstep, mosdegree, and ordinal (not recommended except for maybe 5L 2s) | |||
-- For wording: options include abbreviated or not abbreviated (type in nothing for this option) | |||
-- This function is formerly: | |||
-- function p.mosstep_and_quality_to_degree(mossteps, quality, prefix, notation, wording) | |||
-- Changes: | |||
-- - Encoded mosstep can now be passed directly without passing the mosstep and quality individually. | |||
function p.decode_mosstep_quality(encoded_mosstep, prefix, notation, wording) | |||
-- Get the mossteps and quality from the encoded mosstep | |||
local mossteps = encoded_mosstep["Mossteps"] | |||
local quality = encoded_mosstep["Quality"] | |||
-- Notation options currently include: | |||
-- - mosstep, for intervals | |||
-- - mosdegree, for scale degrees | |||
-- - ordinal, for diatonic-like numbering; can be used for either intervals | |||
-- or scale degrees | |||
local prefix = prefix or "mos" -- Default prefix is mos | |||
local notation = notation or "mosdegree" -- Default notation is mosdegree | |||
local wording = wording or "" -- Default wording is no abbreviations | |||
local degree_name = "" | |||
if wording ~= "abbreviated" then | |||
if notation == "mosstep" then | |||
degree_name = mossteps .. "-" .. prefix .. "step" | |||
elseif notation == "mosdegree" then | |||
degree_name = mossteps .. "-" .. prefix .. "degree" | |||
elseif notation == "ordinal" then | |||
-- Add a dash between the prefix and ordinal, if a prefix is given | |||
if prefix == "" then | |||
degree_name = ord._ordinal(mossteps + 1) | |||
else | |||
degree_name = prefix .. "-" .. ord._ordinal(mossteps + 1) | |||
end | |||
end | |||
if quality == 0 then | |||
degree_name = "Perfect " .. degree_name | |||
elseif quality == 1 then | |||
degree_name = "Major " .. degree_name | |||
elseif quality == 2 then | |||
degree_name = "Augmented " .. degree_name | |||
elseif quality > 2 then | |||
degree_name = (quality - 1) .. "× augmented " .. degree_name | |||
elseif quality == -1 then | |||
degree_name = "Minor " .. degree_name | |||
elseif quality == -2 then | |||
degree_name = "Diminished " .. degree_name | |||
elseif quality < -2 then | |||
degree_name = (math.abs(quality) - 1) .. "× diminished " .. degree_name | |||
end | |||
else | |||
if notation == "mosstep" then | |||
degree_name = mossteps .. prefix .. "s" | |||
elseif notation == "mosdegree" then | |||
degree_name = mossteps .. prefix .. "d" | |||
elseif notation == "ordinal" then | |||
-- Add a dash between the prefix and ordinal, if a prefix is given | |||
if prefix == "" then | |||
degree_name = ord._ordinal(mossteps + 1) | |||
else | |||
degree_name = prefix .. "-" .. ord._ordinal(mossteps + 1) | |||
end | |||
end | |||
if quality == 0 then | |||
degree_name = "P" .. degree_name | |||
elseif quality == 1 then | |||
degree_name = "M" .. degree_name | |||
elseif quality == 2 then | |||
degree_name = "A" .. degree_name | |||
elseif quality > 2 then | |||
degree_name = string.rep("A", quality - 1) .. degree_name | |||
elseif quality == -1 then | |||
degree_name = "m" .. degree_name | |||
elseif quality == -2 then | |||
degree_name = "d" .. degree_name | |||
elseif quality < -2 then | |||
degree_name = string.rep("d", math.abs(quality) - 1) .. degree_name | |||
end | |||
end | |||
return degree_name | |||
end | |||
-- ---------------------------------------------------------------------------- | |||
-- ------------------------ HELPER FUNCTIONS ---------------------------------- | |||
-- ---------------------------------------------------------------------------- | |||
-- Helper function | |||
-- Creates a genchain, or specifically, a nominal-accidental chain. | |||
-- This can only work in one direction at a time, so it's necessary to call this twice, | -- This can only work in one direction at a time, so it's necessary to call this twice, | ||
-- once for each direction (going up by the bright generator, or down). One genchain | -- once for each direction (going up by the bright generator, or down). One genchain | ||
| Line 95: | Line 204: | ||
-- Parameters: | -- Parameters: | ||
-- - input_mos - the mos itself represented as a data structure from Module:MOS | -- - input_mos - the mos itself represented as a data structure from Module:MOS | ||
-- - | -- - upd_gens_per_period - This is either the value u or d for the UDP of up|dp, and is | ||
-- used to calculate the number of initial notes that don't have accidentals. | |||
-- - | -- Note that this is per period, so it's necessary to "simplify" the UDP like a fraction. | ||
-- - chain_length_per_period - the number of notes in the resulting chain. NOTE: if working | |||
-- in terms of "generators stacked after the root", add 1 to that value. | |||
-- - going_up - bool; whether the genchain is going up or down; true for up, false for down | -- - going_up - bool; whether the genchain is going up or down; true for up, false for down | ||
function p.mos_nomacc_chain(input_mos, | function p.mos_nomacc_chain(input_mos, upd_gens_per_period, chain_length_per_period, going_up) | ||
-- Default parameters for testing | -- Default parameters for testing | ||
--[[ | --[[ | ||
local input_mos = input_mos or mos.new(5, 2, 2) | local input_mos = input_mos or mos.new(5, 2, 2) | ||
local | local upd_gens_per_period = upd_gens_per_period or 5 | ||
local | local chain_length_per_period = chain_length_per_period or 14 | ||
local note_symbols = note_symbols or "CDEFGAB" | local note_symbols = note_symbols or "CDEFGAB" | ||
local chroma_symbol = chroma_symbol or "#" | local chroma_symbol = chroma_symbol or "#" | ||
| Line 112: | Line 223: | ||
-- Get the number of mossteps per period and equave | -- Get the number of mossteps per period and equave | ||
local mossteps_per_equave = input_mos.nL + input_mos.ns | local mossteps_per_equave = input_mos.nL + input_mos.ns | ||
local periods_per_equave = | local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns) | ||
local mossteps_per_period = mossteps_per_equave / periods_per_equave | local mossteps_per_period = mossteps_per_equave / periods_per_equave | ||
| Line 134: | Line 245: | ||
-- Get the size of the generator in mossteps | -- Get the size of the generator in mossteps | ||
local gen = mos.bright_gen(input_mos) | local gen = mos.bright_gen(input_mos) | ||
local gen_in_mossteps = gen[ | local gen_in_mossteps = gen["L"] + gen["s"] | ||
-- If the genchain is descending (ie, going_up is false), switch to | -- If the genchain is descending (ie, going_up is false), switch to | ||
| Line 151: | Line 262: | ||
--local genchain = { root } | --local genchain = { root } | ||
local root_offest = (i - 1) * mossteps_per_period -- To make sure that, across all periods, every note has a unique index | local root_offest = (i - 1) * mossteps_per_period -- To make sure that, across all periods, every note has a unique index | ||
-- Create | -- Create the genchain | ||
for j = 1, | local genchain = { } | ||
for j = 1, chain_length_per_period do | |||
-- Convert the accumulator into an index | -- Convert the accumulator into an index | ||
local index = accumulator % mossteps_per_period | local index = accumulator % mossteps_per_period | ||
| Line 164: | Line 272: | ||
-- Add accidentals | -- Add accidentals | ||
-- This is negative if the genchain is descending | -- This is negative if the genchain is descending | ||
-- The upd_gens_per_period refers to the value u (or d for descending chains) in | |||
-- the UDP of up|dp. The first u notes reached by stacking up that many generators from the | |||
-- root don't have accidentals, but the number of notes that don't have accidentals | |||
-- is actually u+1, since the root doesn't have accidentals either. | |||
local accidentals_to_add = 0 | local accidentals_to_add = 0 | ||
if j > | if j > upd_gens_per_period + 1 then | ||
accidentals_to_add = math.ceil((j - | accidentals_to_add = math.ceil((j - upd_gens_per_period - 1) / mossteps_per_period) | ||
end | end | ||
if not going_up then | if not going_up then | ||
| Line 174: | Line 286: | ||
-- Get the final note name | -- Get the final note name | ||
local note_name = {} | local note_name = {} | ||
note_name[ | note_name["Mossteps"] = index + root_offest -- Mossteps needed to reach a note | ||
note_name[ | note_name["Chromas"] = accidentals_to_add -- How many chromas | ||
-- Add the note name | -- Add the note name | ||
table.insert(genchain, note_name) | table.insert(genchain, note_name) | ||
-- Increment the index by the generator | |||
accumulator = accumulator + gen_in_mossteps | |||
end | end | ||
| Line 188: | Line 303: | ||
end | end | ||
-- Helper function | -- Helper function | ||
-- | -- Produces a chain of scale degrees. What scale degrees are | ||
-- reached by stacking a generator? | -- reached by stacking a generator? | ||
-- (EG, major 2nd, augmented 2nd, etc) | -- (EG, major 2nd, augmented 2nd, etc) | ||
| Line 252: | Line 317: | ||
-- - -2 = 1x diminished | -- - -2 = 1x diminished | ||
-- - -3 = 2x diminished | -- - -3 = 2x diminished | ||
function p.mos_degree_chain(input_mos, | -- The following name rules are followed, and numeric values described above are used: | ||
-- - If an interval is the period (including the unison and equave), then the quality is perfect. | |||
-- - For all other intervals, there are two sizes of major (large size) and minor (small size). | |||
-- - For bright generators of non-nL-ns mosses, the sizes are perfect and diminished instead. | |||
-- - For dark generators of non-nL-ns mosses, the sizes are augmented and perfect instead. | |||
-- - Generators of nL ns mosses use the terms major and minor instead. | |||
-- - Alterations denote raising a large interval by a chroma, or lowering a small interval by a chroma. | |||
-- Since non-nL-ns mosses have augmented dark gen and diminished bright gen already, alterations | |||
-- for those are 2x-augmented and 2x-diminished intervals; these are encoded accoringly. | |||
-- Params are as follows: | |||
-- - input_mos: the input mos itself | |||
-- - chain_length_per_period: the number of degrees in the resulting chain. | |||
-- NOTE: if working in terms of "generators stacked after the root", add 1 to that value. | |||
-- - going_up - whether the chain is built on stacking up or down; true for up, false for down | |||
function p.mos_degree_chain(input_mos, chain_length_per_period, going_up) | |||
-- Default parameters for testing | -- Default parameters for testing | ||
--[[ | --[[ | ||
local input_mos = input_mos or mos.new(5, 2, 2) | local input_mos = input_mos or mos.new(5, 2, 2) | ||
local | local chain_length_per_period = chain_length_per_period or 10 | ||
local going_up = false | local going_up = false | ||
]]-- | ]]-- | ||
| Line 262: | Line 341: | ||
-- Get the number of mossteps per period and equave | -- Get the number of mossteps per period and equave | ||
local mossteps_per_equave = input_mos.nL + input_mos.ns | local mossteps_per_equave = input_mos.nL + input_mos.ns | ||
local periods_per_equave = | local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns) | ||
local mossteps_per_period = mossteps_per_equave / periods_per_equave | local mossteps_per_period = mossteps_per_equave / periods_per_equave | ||
-- Get the number of mossteps for the generators | -- Get the number of mossteps for the generators | ||
local bright_gen = mos.bright_gen(input_mos) | local bright_gen = mos.bright_gen(input_mos) | ||
local mossteps_per_bright_gen = bright_gen[ | local mossteps_per_bright_gen = bright_gen["L"] + bright_gen["s"] | ||
local mossteps_per_dark_gen = mossteps_per_period - mossteps_per_bright_gen | local mossteps_per_dark_gen = mossteps_per_period - mossteps_per_bright_gen | ||
| Line 274: | Line 353: | ||
local chain_for_period = {} | local chain_for_period = {} | ||
for i = 1, | for i = 1, chain_length_per_period do | ||
-- Calculate mossteps | -- Calculate mossteps | ||
| Line 317: | Line 396: | ||
-- Put together the name | -- Put together the name | ||
local degree = { [ | local degree = { ["Mossteps"] = mossteps, ["Quality"] = quality } | ||
table.insert(chain_for_period, degree) | table.insert(chain_for_period, degree) | ||
end | end | ||