Module:MOS gamut: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
ArrowHead294 (talk | contribs) mNo edit summary |
||
| (42 intermediate revisions by 3 users not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local et = require("Module:ET") | |||
local mos = require("Module:MOS") | |||
local mosm = require("Module:MOS modes") | |||
local mosnot = require("Module:MOS notation") | |||
local rat = require("Module:Rational") | |||
local utils = require("Module:Utils") | |||
local yesno = require("Module:Yesno") | |||
-- | -- Helper function for the function that has "frame" as a parameter | ||
function p.mos_gamut(input_mos, | function p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol) | ||
-- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) | -- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) | ||
local input_mos = input_mos or mos.new(5, 2, 2) | local input_mos = input_mos or mos.new(5, 2, 2) | ||
| Line 110: | Line 17: | ||
-- 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 | local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns) | ||
local mossteps_per_period = mossteps_per_equave / | local mossteps_per_period = mossteps_per_equave / periods_per_equave | ||
-- Some default params will be different if the scalesig is 5L 2s | -- Some default params will be different if the scalesig is 5L 2s | ||
local scale_sig = mos.as_string(input_mos) | local scale_sig = mos.as_string(input_mos) | ||
-- The default | -- The default UDP corresponds to the middle mode. For mosses with an even | ||
-- | -- number of modes, there are two middle modes, so use the brighter of the | ||
local | -- two instead. | ||
-- If it's 5L 2s, default to the second-brightest mode. | |||
local udp_default = { periods_per_equave * math.ceil((mossteps_per_period - 1)/ 2), periods_per_equave * math.floor((mossteps_per_period - 1) / 2) } | |||
if scale_sig == "5L 2s" then | if scale_sig == "5L 2s" then | ||
udp_default = { 5, 1 } | |||
end | end | ||
local generators_up = | local udp = udp or udp_default | ||
local generators_up = udp[1] | |||
local generators_down = udp[2] | |||
-- The natural note symbols are those that correspond to diamond-mos | -- The natural note symbols are those that correspond to diamond-mos | ||
| Line 144: | Line 55: | ||
local chroma_plus_symbol = chroma_plus_symbol or chroma_plus_default | local chroma_plus_symbol = chroma_plus_symbol or chroma_plus_default | ||
local chroma_minus_symbol = chroma_minus_symbol or chroma_minus_default | local chroma_minus_symbol = chroma_minus_symbol or chroma_minus_default | ||
-- How long is the inital genchain for notes without accidentals? | -- How long is the inital genchain for notes without accidentals? | ||
local gens_up_per_period = generators_up / | local gens_up_per_period = generators_up / periods_per_equave | ||
local gens_down_per_period = generators_down / | local gens_down_per_period = generators_down / periods_per_equave | ||
-- Get and simplify the step ratio | -- Get and simplify the step ratio | ||
local kp = step_ratio[1] | local kp = step_ratio[1] | ||
local kq = step_ratio[2] | local kq = step_ratio[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 | ||
-- How many large and small steps per period? | -- How many large and small steps per period? | ||
local x = input_mos.nL / | local x = input_mos.nL / periods_per_equave -- Large step count | ||
local y = input_mos.ns / | local y = input_mos.ns / periods_per_equave -- Small step count | ||
-- How many esteps are in the equave? Gamut does not include any notes reached by | -- How many esteps are in the equave? Gamut does not include any notes reached by | ||
-- increments smaller than a chroma, so if the step ratio is not simplified, the | -- increments smaller than a chroma, so if the step ratio is not simplified, the | ||
-- gamut returned will be for a simplified step ratio | -- gamut returned will be for a simplified step ratio | ||
local | local esteps_per_equave = input_mos.nL * num + input_mos.ns * den | ||
-- Similarly, how many esteps per period? | -- Similarly, how many esteps per period? | ||
local | local esteps_per_period = x * num + y * den | ||
-- How long should the genchain extend after the initial genchain? | -- How long should the genchain extend after the initial genchain? | ||
| Line 180: | Line 84: | ||
-- For any other ratio p:q (simplified), do this calculation: | -- For any other ratio p:q (simplified), do this calculation: | ||
-- x*floor(p/2) + y*floor(q/2) | -- x*floor(p/2) + y*floor(q/2) | ||
-- This is such that each altered note (what would be the black keys on a piano) | |||
-- has names that contain the fewest chromas possible, even if they have more than | |||
-- one name. EG, standard notation has C#/Db have two names, but both names | |||
-- have the fewest possible accidentals | |||
local genchain_extend = 0 | local genchain_extend = 0 | ||
if num / den == 2 then | if num / den == 2 then | ||
| Line 189: | Line 97: | ||
end | end | ||
-- How long are the genchains? | -- How long are the genchains? Length is per period | ||
local ascending_genchain_length = gens_up_per_period + genchain_extend | -- Genchain length counts the root, hence the +1 | ||
local descending_genchain_length = gens_down_per_period + genchain_extend | local ascending_genchain_length = gens_up_per_period + genchain_extend + 1 | ||
local descending_genchain_length = gens_down_per_period + genchain_extend + 1 | |||
-- Get the ascending and descending genchains | -- Get the ascending and descending genchains | ||
local ascending_genchain = | -- The genchains are notationally agnostic so notation needs to be applied to them | ||
local descending_genchain = | local ascending_genchain = mosnot.mos_nomacc_chain(input_mos, gens_up_per_period, ascending_genchain_length, true) | ||
local descending_genchain = mosnot.mos_nomacc_chain(input_mos, gens_down_per_period, descending_genchain_length, false) | |||
-- Create an empty gamut | -- Create an empty gamut | ||
local gamut = {} | local gamut = {} | ||
for i = 1, | for i = 1, esteps_per_equave + 1 do | ||
table.insert(gamut, "") | table.insert(gamut, "") | ||
end | end | ||
| Line 205: | Line 115: | ||
-- How many esteps are the bright and dark generators? | -- How many esteps are the bright and dark generators? | ||
local bright_gen = mos.bright_gen(input_mos) | local bright_gen = mos.bright_gen(input_mos) | ||
local esteps_per_bright_gen = bright_gen[ | local esteps_per_bright_gen = bright_gen["L"] * num + bright_gen["s"] * den | ||
local esteps_per_dark_gen = | local esteps_per_dark_gen = esteps_per_period - esteps_per_bright_gen | ||
-- Add the notes to the gamut | -- Add the notes to the gamut | ||
for j = 1, periods_per_equave do | |||
for j = 1, | |||
local bright_accumulator = 0 | local bright_accumulator = 0 | ||
for i = 1, #ascending_genchain[j] do | for i = 1, #ascending_genchain[j] do | ||
local index = (bright_accumulator % | local index = (bright_accumulator % esteps_per_period) + (j - 1) * esteps_per_period + 1 | ||
gamut[index] = gamut[index] .. | |||
-- Convert the notationally agnostic form into a form that uses given notation | |||
local note = ascending_genchain[j][i] | |||
local note_symbol = string.sub(note_symbols, note["Mossteps"] + 1, note["Mossteps"] + 1) | |||
local chroma_count = note["Chromas"] | |||
local note_name = note_symbol .. string.rep(chroma_plus_symbol, chroma_count) | |||
gamut[index] = gamut[index] .. note_name | |||
bright_accumulator = bright_accumulator + esteps_per_bright_gen | bright_accumulator = bright_accumulator + esteps_per_bright_gen | ||
end | end | ||
local dark_accumulator = esteps_per_dark_gen | local dark_accumulator = esteps_per_dark_gen | ||
for i = 2, #descending_genchain[j] do | for i = 2, #descending_genchain[j] do | ||
local index = (dark_accumulator % | local index = (dark_accumulator % esteps_per_period) + (j - 1) * esteps_per_period + 1 | ||
-- Convert the notationally agnostic form into a form that uses given notation | |||
local note = descending_genchain[j][i] | |||
local note_symbol = string.sub(note_symbols, note["Mossteps"] + 1, note["Mossteps"] + 1) | |||
local chroma_count = note["Chromas"] * -1 | |||
local note_name = note_symbol .. string.rep(chroma_minus_symbol, chroma_count) | |||
-- Add to gamut | |||
-- If there is a note there already, then append and separate with a slash | |||
if gamut[index] ~= "" then | if gamut[index] ~= "" then | ||
gamut[index] = gamut[index] .. "/" .. | gamut[index] = gamut[index] .. "/" .. note_name | ||
else | else | ||
gamut[index] = gamut[index] .. | gamut[index] = gamut[index] .. note_name | ||
end | end | ||
dark_accumulator = dark_accumulator + esteps_per_dark_gen | dark_accumulator = dark_accumulator + esteps_per_dark_gen | ||
| Line 230: | Line 155: | ||
-- Last note in the gamut is the root up one equave | -- Last note in the gamut is the root up one equave | ||
gamut[#gamut] = | gamut[#gamut] = gamut[1] | ||
return gamut | return gamut | ||
end | end | ||
function p.mos_gamut_frame(frame) | function p.mos_gamut_frame(frame) | ||
-- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) | -- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio) | ||
local input_mos_unparsed = frame.args[ | local input_mos_unparsed = frame.args["Scale Signature"] | ||
local input_mos = mos.parse(input_mos_unparsed) or mos.new(2, 5, 2) | local input_mos = mos.parse(input_mos_unparsed) or mos.new(2, 5, 2) | ||
-- Step ratio | -- Step ratio | ||
local step_ratio = { 2, 1 } | local step_ratio = { 2, 1 } | ||
if string.len(frame.args[ | if string.len(frame.args["Step Ratio"]) > 0 then | ||
step_ratio = | step_ratio = mosnot.parse_step_ratio(frame.args["Step Ratio"]) | ||
end | end | ||
-- 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 | local periods_per_equave = utils._gcd(input_mos.nL, input_mos.ns) | ||
local mossteps_per_period = mossteps_per_equave / | local mossteps_per_period = mossteps_per_equave / periods_per_equave | ||
-- If certain params were left blank and the scalesig is 5L 2s, the default | -- If certain params were left blank and the scalesig is 5L 2s, the default | ||
| Line 268: | Line 180: | ||
local scale_sig = mos.as_string(input_mos) | local scale_sig = mos.as_string(input_mos) | ||
-- The default | -- The default UDP corresponds to the middle mode. For mosses with an even | ||
-- | -- number of modes, there are two middle modes, so use the brighter of the | ||
local | -- two instead. | ||
-- If it's 5L 2s, default to the second-brightest mode. | |||
local udp = { periods_per_equave * math.ceil((mossteps_per_period - 1)/ 2), periods_per_equave * math.floor((mossteps_per_period - 1) / 2) } | |||
if scale_sig == "5L 2s" then | if scale_sig == "5L 2s" then | ||
udp = { 5, 1 } | |||
end | end | ||
if string.len(frame.args["UDP"]) > 0 then | |||
if string.len(frame.args[ | udp = mosnot.parse_udp(frame.args["UDP"]) | ||
end | end | ||
local generators_up = udp[1] | |||
local generators_down = udp[2] | |||
-- Get | -- Get notation: naturals (or nominals), sharp symbol, and flat symbol | ||
local notation_default = { ["Naturals"] = string.sub("JKLMNOPQRSTUVWXYZ", 1, mossteps_per_equave), ["Sharp"] = "&", ["Flat"] = "@" } | |||
local | |||
if scale_sig == "5L 2s" then | if scale_sig == "5L 2s" then | ||
notation_default["Naturals"] = "CDEFGAB" | |||
notation_default["Sharp"] = "#" | |||
notation_default["Flat"] = "b" | |||
end | end | ||
local notation = mosnot.parse_notation(frame.args["Notation"]) or notation_default | |||
local note_symbols = notation["Naturals"] | |||
local chroma_plus_symbol = notation["Sharp"] | |||
local chroma_minus_symbol = notation["Flat"] | |||
-- Get the gamut | -- Get the gamut | ||
local gamut = p.mos_gamut(input_mos, | local gamut = p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol) | ||
-- | -- Since the gamut on a mos page is just text, so will this | ||
-- Formatting options may be explored at a later date | |||
local result = "" | |||
-- | for i = 1, #gamut - 1 do | ||
-- If the note name does not contain accidentals, it's a natural and should be bold | |||
local result = | |||
for i = 1, #gamut do | |||
-- | |||
local note_name = gamut[i] | local note_name = gamut[i] | ||
if string.match(note_name, chroma_plus_symbol) or string.match(note_name, chroma_minus_symbol) then | |||
result = result .. note_name .. ", " | |||
result = result | |||
else | else | ||
result = result .. ' | result = result .. "'''" .. note_name .. "''', " | ||
end | end | ||
end | end | ||
result = result .. " | result = result .. "'''" .. gamut[#gamut] .. "'''" | ||
-- Debugger option | |||
local debugg = yesno(frame.args["debug"]) | |||
if debugg == true then | |||
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>" | |||
end | |||
return result | return frame:preprocess(result) | ||
end | end | ||
return p | return p | ||
Latest revision as of 12:29, 1 June 2025
- This module should not be invoked directly; use its corresponding template instead: Template:MOS gamut.
This module produces a gamut (sequence of note names with accidentals) for an edo.
| Introspection summary for Module:MOS gamut | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||||||||||||||||||||||||||
No function descriptions were provided. The Lua code may have further information.
local p = {}
local et = require("Module:ET")
local mos = require("Module:MOS")
local mosm = require("Module:MOS modes")
local mosnot = require("Module:MOS notation")
local rat = require("Module:Rational")
local utils = require("Module:Utils")
local yesno = require("Module:Yesno")
-- Helper function for the function that has "frame" as a parameter
function p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol)
-- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio)
local input_mos = input_mos or mos.new(5, 2, 2)
local step_ratio = step_ratio or { 2, 1 }
-- 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
-- Some default params will be different if the scalesig is 5L 2s
local scale_sig = mos.as_string(input_mos)
-- The default UDP corresponds to the middle mode. For mosses with an even
-- number of modes, there are two middle modes, so use the brighter of the
-- two instead.
-- If it's 5L 2s, default to the second-brightest mode.
local udp_default = { periods_per_equave * math.ceil((mossteps_per_period - 1)/ 2), periods_per_equave * math.floor((mossteps_per_period - 1) / 2) }
if scale_sig == "5L 2s" then
udp_default = { 5, 1 }
end
local udp = udp or udp_default
local generators_up = udp[1]
local generators_down = udp[2]
-- The natural note symbols are those that correspond to diamond-mos
-- (JKLMN...) unless the mos is 5L 2s, then it's CDEFGAB
-- If it's diamond-mos, gamut is limited to 17 note names
local note_symbols_main = "JKLMNOPQRSTUVWXYZ"
local note_symbols_default = string.sub(note_symbols_main, 1, mossteps_per_equave)
if scale_sig == "5L 2s" then
note_symbols_default = "CDEFGAB"
end
local note_symbols = note_symbols or note_symbols_default
-- The default accidentals are the amp and at (& and @)
-- unless the mos is 5L 2s, then it's sharp and flat (# and b)
local chroma_plus_default = "&"
local chroma_minus_default = "@"
if scale_sig == "5L 2s" then
chroma_plus_default = "#"
chroma_minus_default = "b"
end
local chroma_plus_symbol = chroma_plus_symbol or chroma_plus_default
local chroma_minus_symbol = chroma_minus_symbol or chroma_minus_default
-- How long is the inital genchain for notes without accidentals?
local gens_up_per_period = generators_up / periods_per_equave
local gens_down_per_period = generators_down / periods_per_equave
-- Get and simplify the step ratio
local kp = step_ratio[1]
local kq = step_ratio[2]
local k = utils._gcd(kp, kq)
local num = kp / k
local den = kq / k
-- How many large and small steps per period?
local x = input_mos.nL / periods_per_equave -- Large step count
local y = input_mos.ns / periods_per_equave -- Small step count
-- How many esteps are in the equave? Gamut does not include any notes reached by
-- increments smaller than a chroma, so if the step ratio is not simplified, the
-- gamut returned will be for a simplified step ratio
local esteps_per_equave = input_mos.nL * num + input_mos.ns * den
-- Similarly, how many esteps per period?
local esteps_per_period = x * num + y * den
-- How long should the genchain extend after the initial genchain?
-- For a basic step ratio 2:1, extend by x
-- For a collapsed or equalized step ratio, don't extend at all
-- For any other ratio p:q (simplified), do this calculation:
-- x*floor(p/2) + y*floor(q/2)
-- This is such that each altered note (what would be the black keys on a piano)
-- has names that contain the fewest chromas possible, even if they have more than
-- one name. EG, standard notation has C#/Db have two names, but both names
-- have the fewest possible accidentals
local genchain_extend = 0
if num / den == 2 then
genchain_extend = x
elseif num == den or den == 0 then
genchain_extend = 0
else
genchain_extend = x * math.floor(num/2) + y * math.floor(den/2)
end
-- How long are the genchains? Length is per period
-- Genchain length counts the root, hence the +1
local ascending_genchain_length = gens_up_per_period + genchain_extend + 1
local descending_genchain_length = gens_down_per_period + genchain_extend + 1
-- Get the ascending and descending genchains
-- The genchains are notationally agnostic so notation needs to be applied to them
local ascending_genchain = mosnot.mos_nomacc_chain(input_mos, gens_up_per_period, ascending_genchain_length, true)
local descending_genchain = mosnot.mos_nomacc_chain(input_mos, gens_down_per_period, descending_genchain_length, false)
-- Create an empty gamut
local gamut = {}
for i = 1, esteps_per_equave + 1 do
table.insert(gamut, "")
end
-- How many esteps are the bright and dark generators?
local bright_gen = mos.bright_gen(input_mos)
local esteps_per_bright_gen = bright_gen["L"] * num + bright_gen["s"] * den
local esteps_per_dark_gen = esteps_per_period - esteps_per_bright_gen
-- Add the notes to the gamut
for j = 1, periods_per_equave do
local bright_accumulator = 0
for i = 1, #ascending_genchain[j] do
local index = (bright_accumulator % esteps_per_period) + (j - 1) * esteps_per_period + 1
-- Convert the notationally agnostic form into a form that uses given notation
local note = ascending_genchain[j][i]
local note_symbol = string.sub(note_symbols, note["Mossteps"] + 1, note["Mossteps"] + 1)
local chroma_count = note["Chromas"]
local note_name = note_symbol .. string.rep(chroma_plus_symbol, chroma_count)
gamut[index] = gamut[index] .. note_name
bright_accumulator = bright_accumulator + esteps_per_bright_gen
end
local dark_accumulator = esteps_per_dark_gen
for i = 2, #descending_genchain[j] do
local index = (dark_accumulator % esteps_per_period) + (j - 1) * esteps_per_period + 1
-- Convert the notationally agnostic form into a form that uses given notation
local note = descending_genchain[j][i]
local note_symbol = string.sub(note_symbols, note["Mossteps"] + 1, note["Mossteps"] + 1)
local chroma_count = note["Chromas"] * -1
local note_name = note_symbol .. string.rep(chroma_minus_symbol, chroma_count)
-- Add to gamut
-- If there is a note there already, then append and separate with a slash
if gamut[index] ~= "" then
gamut[index] = gamut[index] .. "/" .. note_name
else
gamut[index] = gamut[index] .. note_name
end
dark_accumulator = dark_accumulator + esteps_per_dark_gen
end
end
-- Last note in the gamut is the root up one equave
gamut[#gamut] = gamut[1]
return gamut
end
function p.mos_gamut_frame(frame)
-- Default parameters for input mos and step ratio (5L 2s and 2:1 step ratio)
local input_mos_unparsed = frame.args["Scale Signature"]
local input_mos = mos.parse(input_mos_unparsed) or mos.new(2, 5, 2)
-- Step ratio
local step_ratio = { 2, 1 }
if string.len(frame.args["Step Ratio"]) > 0 then
step_ratio = mosnot.parse_step_ratio(frame.args["Step Ratio"])
end
-- 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
-- If certain params were left blank and the scalesig is 5L 2s, the default
-- params will be for standard notation
local scale_sig = mos.as_string(input_mos)
-- The default UDP corresponds to the middle mode. For mosses with an even
-- number of modes, there are two middle modes, so use the brighter of the
-- two instead.
-- If it's 5L 2s, default to the second-brightest mode.
local udp = { periods_per_equave * math.ceil((mossteps_per_period - 1)/ 2), periods_per_equave * math.floor((mossteps_per_period - 1) / 2) }
if scale_sig == "5L 2s" then
udp = { 5, 1 }
end
if string.len(frame.args["UDP"]) > 0 then
udp = mosnot.parse_udp(frame.args["UDP"])
end
local generators_up = udp[1]
local generators_down = udp[2]
-- Get notation: naturals (or nominals), sharp symbol, and flat symbol
local notation_default = { ["Naturals"] = string.sub("JKLMNOPQRSTUVWXYZ", 1, mossteps_per_equave), ["Sharp"] = "&", ["Flat"] = "@" }
if scale_sig == "5L 2s" then
notation_default["Naturals"] = "CDEFGAB"
notation_default["Sharp"] = "#"
notation_default["Flat"] = "b"
end
local notation = mosnot.parse_notation(frame.args["Notation"]) or notation_default
local note_symbols = notation["Naturals"]
local chroma_plus_symbol = notation["Sharp"]
local chroma_minus_symbol = notation["Flat"]
-- Get the gamut
local gamut = p.mos_gamut(input_mos, udp, step_ratio, note_symbols, chroma_plus_symbol, chroma_minus_symbol)
-- Since the gamut on a mos page is just text, so will this
-- Formatting options may be explored at a later date
local result = ""
for i = 1, #gamut - 1 do
-- If the note name does not contain accidentals, it's a natural and should be bold
local note_name = gamut[i]
if string.match(note_name, chroma_plus_symbol) or string.match(note_name, chroma_minus_symbol) then
result = result .. note_name .. ", "
else
result = result .. "'''" .. note_name .. "''', "
end
end
result = result .. "'''" .. gamut[#gamut] .. "'''"
-- Debugger option
local debugg = yesno(frame.args["debug"])
if debugg == true then
result = "<syntaxhighlight lang=\"wikitext\">" .. result .. "</syntaxhighlight>"
end
return frame:preprocess(result)
end
return p