Module:MOS: Difference between revisions
No edit summary |
ArrowHead294 (talk | contribs) m Comment formatting |
||
| Line 1: | Line 1: | ||
-- Module for working with mosses in lua code; this serves as a "library" for | --[[ | ||
Module for working with mosses in lua code; this serves as a "library" for | |||
mos-related modules and thus does not have a corresponding template. | |||
Functionality includes: | |||
- Creating/parsing mosses | |||
- Creating scalesigs (string representations) of mosses | |||
- Finding certain modes of a mos | |||
- Finding generators for a mos | |||
- Producing vectors for simple mos intervals | |||
- Interval arithmetic, in the form of adding vectors of L's and s's, and | |||
period/equave-reducing intervals | |||
- Finding equal tunings for mosses | |||
]]-- | |||
local rat = require("Module:Rational") | local rat = require("Module:Rational") | ||
local utils = require("Module:Utils") | local utils = require("Module:Utils") | ||
local et = require("Module:ET") | local et = require("Module:ET") | ||
local tip = require("Module:Template input parse") | |||
local p = {} | local p = {} | ||
-- Naming scheme for function names: | --[[ | ||
Naming scheme for function names: | |||
- Functions related to mosses don't have any special names. | |||
- Functions related to a mos's modes generally end with "mode". | |||
- Functions related to a mos's generators, equave, or period contain the | |||
corresponding interval as part of its name. | |||
- Functions related to intervals generally begin with "interval". | |||
- Interval complement/reduce functions end with "complement" and "reduce". | |||
- Functions that produce strings generally have the phrase "as string". | |||
- Functions that "count" something generally end with "count". | |||
- If a function requires an interval and mos as input, the interval(s) come | |||
after the mos. | |||
- Functions that have to do with equal tunings will have "et" in its name. | |||
]]-- | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
| Line 57: | Line 64: | ||
end | end | ||
-- | -- Parse a mos from its scalesig. | ||
function p.parse(unparsed) | function p.parse(unparsed) | ||
local nL, ns, equave = unparsed:match("^(%d+)[Ll]%s*(%d+)[Ss]%s*(.*)$") | local nL, ns, equave = unparsed:match("^(%d+)[Ll]%s*(%d+)[Ss]%s*(.*)$") | ||
| Line 75: | Line 82: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Construct a string representation (scalesig) for a MOS structure. | --[[ | ||
Construct a string representation (scalesig) for a MOS structure. | |||
Scalesig is "xL ys", or "xL ys<p/q>" for nonoctave scales. | |||
Option to use nbsp is provided using the second param; default is no nbsp | |||
]]-- | |||
function p.as_string(mos, use_nbsp) | function p.as_string(mos, use_nbsp) | ||
local use_nbsp = use_nbsp or true | local use_nbsp = use_nbsp or true | ||
| Line 87: | Line 96: | ||
end | end | ||
-- Construct a longer string representation for a MOS structure. | --[[ | ||
Construct a longer string representation for a MOS structure. | |||
Scalesig is "xL ys", or "xL ys (p/q-equivalent)" for nonoctave scales. | |||
Option to use nbsp is provided using the second param; default is no nbsp | |||
]]-- | |||
function p.as_long_string(mos, use_nbsp) | function p.as_long_string(mos, use_nbsp) | ||
local use_nbsp = use_nbsp or true | local use_nbsp = use_nbsp or true | ||
| Line 100: | Line 113: | ||
end | end | ||
-- Given an interval as a vector of L's and s's, produce a string "iL + js", | --[[ | ||
Given an interval as a vector of L's and s's, produce a string "iL + js", | |||
where i and j are the quantities for L and s. | |||
]]-- | |||
function p.interval_as_string(interval) | function p.interval_as_string(interval) | ||
| Line 162: | Line 177: | ||
------------------------------- MODE FUNCTIONS --------------------------------- | ------------------------------- MODE FUNCTIONS --------------------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--[[ | |||
-- Find the brightest (true-mos) mode of a mos, as a string of L's and s's. | Find the brightest (true-mos) mode of a mos, as a string of L's and s's. | ||
Calculation is based on the definition of a Christoffel word, as the closest | |||
integer approximation to line y = #s/#L*x. | |||
]]-- | |||
function p.brightest_mode(mos) | function p.brightest_mode(mos) | ||
local nL = mos.nL | local nL = mos.nL | ||
| Line 188: | Line 205: | ||
end | end | ||
-- Find the darkest true-mos mode of a mos. | |||
-- Find the darkest true-mos mode of a mos. It's the reverse of the brightest mode. | |||
function p.darkest_mode(mos) | function p.darkest_mode(mos) | ||
local nL = mos.nL | local nL = mos.nL | ||
| Line 213: | Line 230: | ||
end | end | ||
-- Given a mos, return a mode based on how it's ranked by modal brightness. | --[[ | ||
Given a mos, return a mode based on how it's ranked by modal brightness. | |||
Ordering here is based on the number of bright gens going DOWN PER PERIOD: | |||
0 is the brightest mode, 1 is 2nd brightest, etc... | |||
]]-- | |||
function p.mode_from_mos(mos, bright_gens_down) | function p.mode_from_mos(mos, bright_gens_down) | ||
return p.rotate_mode(p.brightest_mode(mos), bright_gens_down * p.bright_gen_step_count(mos)) | return p.rotate_mode(p.brightest_mode(mos), bright_gens_down * p.bright_gen_step_count(mos)) | ||
| Line 239: | Line 259: | ||
end | end | ||
-- List all unique rotations for a mode. Order of modes is by rotation. | --[[ | ||
List all unique rotations for a mode. Order of modes is by rotation. | |||
Note: there will always be s/p modes, where s is the number of steps in the | |||
entered mode, and p is the period of repetition. At most, there will be s | |||
modes, but if there is a substring of length p that repeats within the mode | |||
(where p divides s with remainder = 0), then there will be p modes. It's also | |||
possible to have only one mode, but this can only happen if there is only one | |||
step size, meaning it's a unary scale (only one step size). | |||
]]-- | |||
function p.mode_rotations(mode_string) | function p.mode_rotations(mode_string) | ||
local rotations = {} | local rotations = {} | ||
| Line 258: | Line 281: | ||
end | end | ||
-- Rotate a mode by shifting the step sequence to the left. Negative values | --[[ | ||
Rotate a mode by shifting the step sequence to the left. Negative values | |||
shift it to the right. Helper function for mode_from_mos(). | |||
]]-- | |||
function p.rotate_mode(mode_string, shift_amt) | function p.rotate_mode(mode_string, shift_amt) | ||
local shift_amt = shift_amt == nil and 1 or shift_amt % #mode_string -- Default is 1 | local shift_amt = shift_amt == nil and 1 or shift_amt % #mode_string -- Default is 1 | ||
| Line 270: | Line 295: | ||
---------------------------- STEP MATRIX FUNCTIONS ----------------------------- | ---------------------------- STEP MATRIX FUNCTIONS ----------------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--[[ | |||
-- Convert a single mode (as a string) into a step matrix. This is a listing of | Convert a single mode (as a string) into a step matrix. This is a listing of | ||
every interval's step vector in the mode. | |||
]]-- | |||
function p.mode_to_step_matrix(mode_string) | function p.mode_to_step_matrix(mode_string) | ||
local matrix = {} | local matrix = {} | ||
| Line 282: | Line 308: | ||
end | end | ||
-- Given a mos, produce every step matrix for every mode. Modes are listed in | --[[ | ||
Given a mos, produce every step matrix for every mode. Modes are listed in | |||
order of brightness. | |||
]]-- | |||
function p.modes_to_step_matrices(mos) | function p.modes_to_step_matrices(mos) | ||
local modes = p.modes_by_brightness(mos) | local modes = p.modes_by_brightness(mos) | ||
| Line 294: | Line 322: | ||
end | end | ||
-- Given a single mode (as a string), produce the step matrices for each | --[[ | ||
Given a single mode (as a string), produce the step matrices for each | |||
rotation of that mode. Modes are listed in order of rotation. | |||
]]-- | |||
function p.mode_rotations_to_step_matrices(mode_string) | function p.mode_rotations_to_step_matrices(mode_string) | ||
local modes = p.mode_rotations(mode_string) | local modes = p.mode_rotations(mode_string) | ||
| Line 306: | Line 336: | ||
end | end | ||
-- Given an input mos, produce its modal union. | --[[ | ||
Given an input mos, produce its modal union. | |||
This is a listing of every interval's large and small sizes. | |||
]]-- | |||
function p.modal_union(input_mos) | function p.modal_union(input_mos) | ||
local brightest_mode = p.brightest_mode(input_mos) | local brightest_mode = p.brightest_mode(input_mos) | ||
| Line 336: | Line 369: | ||
------------------ (IE, GENERATORS AND PERIOD INTERVALS) ----------------------- | ------------------ (IE, GENERATORS AND PERIOD INTERVALS) ----------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--[[ | |||
-- Compute the bright gen as a vector of L's and s's. | Compute the bright gen as a vector of L's and s's. | ||
Bright gen has two sizes: perfect (large) and diminished (small). The size | |||
given by this function is the large size. | |||
]]-- | |||
function p.bright_gen(mos) | function p.bright_gen(mos) | ||
local nL = mos.nL | local nL = mos.nL | ||
| Line 370: | Line 405: | ||
end | end | ||
-- Compute the dark gen as a vector of L's and s's. | --[[ | ||
Compute the dark gen as a vector of L's and s's. | |||
-- | Dark gen has two sizes: augmented (large) and perfect (small). The size given | ||
by this function is the small size and is equal to the period complement of the | |||
bright gen. | |||
]]-- | |||
function p.dark_gen(mos) | function p.dark_gen(mos) | ||
local bright_gen = p.bright_gen(mos) | local bright_gen = p.bright_gen(mos) | ||
| Line 379: | Line 417: | ||
end | end | ||
-- Compute the period as a vector of L's and s's. | -- Compute the period as a vector of L's and s's. Period intervals only have one size: perfect. | ||
function p.period(mos) | function p.period(mos) | ||
local gcd = utils._gcd(mos.nL, mos.ns) | local gcd = utils._gcd(mos.nL, mos.ns) | ||
| Line 389: | Line 426: | ||
end | end | ||
-- Compute the equave as a vector of L's and s's. | --[[ | ||
Compute the equave as a vector of L's and s's. | |||
Equave intervals only have one size: perfect. Equave and period intervals are | |||
the same for single-period mosses. | |||
]]-- | |||
function p.equave(mos) | function p.equave(mos) | ||
return { | return { | ||
| Line 402: | Line 442: | ||
------------------ INTERVAL FUNCTIONS FOR SIMPLE INTERVALS --------------------- | ------------------ INTERVAL FUNCTIONS FOR SIMPLE INTERVALS --------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--[[ | |||
-- Compute the unison as a vector of L's and s's. | Compute the unison as a vector of L's and s's. | ||
The unison is denoted by moving up from the root by zero steps, and thus does | |||
not need a mos as input. It's basically a zero vector. | |||
The unison only has one size: perfect. | |||
]]-- | |||
function p.unison() | function p.unison() | ||
return { ["L"] = 0, ["s"] = 0 } | return { ["L"] = 0, ["s"] = 0 } | ||
end | end | ||
-- Compute the vector for a single chroma. It's a large step minus a small step. | --[[ | ||
Compute the vector for a single chroma. It's a large step minus a small step. | |||
Adding or subtracting any interval by this interval changes its "size". | |||
]]-- | |||
function p.chroma() | function p.chroma() | ||
return { ["L"] = 1, ["s"] = -1 } | return { ["L"] = 1, ["s"] = -1 } | ||
| Line 446: | Line 491: | ||
end | end | ||
-- Compute an arbitrary mos interval as a vector of L's and s's. | --[[ | ||
Compute an arbitrary mos interval as a vector of L's and s's. | |||
The step_count param is the number of mossteps in the interval. EG, in 5L 2s, | |||
the large 2-mosstep is "LL", so the corresponding vector has L=2, s=0. | |||
Mossteps larger than the equave (eg, the minor 9th in non-xen music theory) | |||
are allowed. | |||
The size_offset denotes whether the interval is the large size (0) or the | |||
small size (-1). This can exceed the range of [-1, 0] to represent intervals | |||
raised/lowered by multiple chromas (augmented, diminished, etc). | |||
Note that for period intervals (eg, the root and equave), there is only one | |||
size (0 = perfect), so -1 is diminished and 1 is augmented. | |||
E.G., a perfect 4-diastep (perf. 5th) is 4 steps. Since it's the large size, | |||
the offset is 0, but to get the diminished 5th, the offset should be -1. | |||
]]-- | |||
function p.interval_from_mos(mos, step_count, size_offset) | function p.interval_from_mos(mos, step_count, size_offset) | ||
local size_offset = size_offset or 0 -- Optional param; defaults to large size | local size_offset = size_offset or 0 -- Optional param; defaults to large size | ||
| Line 471: | Line 523: | ||
end | end | ||
-- Compute an arbitrary mos interval (as a string of steps) as a vector of L's | --[[ | ||
Compute an arbitrary mos interval (as a string of steps) as a vector of L's | |||
and s's. This also serves as a helper function for p.interval_from_mos(). | |||
Sequences of steps can be entered, where each step is one of five sizes: | |||
- L: large step. | |||
- s: small step. | |||
- c: a chroma; the difference between a large and small step. | |||
- A: an augmented step; a large step plus a chroma. | |||
- d: a diminished step, or diesis; a small step minus a chroma. | |||
]]-- | |||
function p.interval_from_step_sequence(step_sequence) | function p.interval_from_step_sequence(step_sequence) | ||
local mossteps = #step_sequence | local mossteps = #step_sequence | ||
| Line 531: | Line 586: | ||
end | end | ||
-- Given a vector representing an interval, compute the number of mossteps it | --[[ | ||
Given a vector representing an interval, compute the number of mossteps it | |||
corresponds to. Knowledge of the corresponding mos is not needed. Intervals | |||
can be negative, resulting in a negative output. | |||
]]-- | |||
function p.interval_step_count(interval) | function p.interval_step_count(interval) | ||
return interval["L"] + interval["s"] | return interval["L"] + interval["s"] | ||
end | end | ||
-- Given a vector representing an interval, compute the number of chromas it was | --[[ | ||
Given a vector representing an interval, compute the number of chromas it was | |||
raised or lowered by from its large size (for non-period intervals) or its | |||
perfect size (for period/root/equave intervals). This requires the mos as | |||
input. | |||
If the number of chromas from a small (EG minor) interval is desired, then | |||
using the param size_offset can be used: 0 for chromas from large size, -1 | |||
for chromas from small size. This can exceed the range [-1, 0] if needed. | |||
E.G., a diminished 2-diastep (dim. 3rd) has the vector {0,2}. It's reached by | |||
either lowering the major 2-step by 2 chromas, or lowering the minor 2-step | |||
by 1 chroma. | |||
]]-- | |||
function p.interval_chroma_count(interval, mos, size_offset) | function p.interval_chroma_count(interval, mos, size_offset) | ||
local size_offset = size_offset or 0 -- Default of 0. | local size_offset = size_offset or 0 -- Default of 0. | ||
| Line 593: | Line 654: | ||
---------------------- INTERVAL MANIPULATION FUNCTIONS ------------------------- | ---------------------- INTERVAL MANIPULATION FUNCTIONS ------------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--[[ | |||
-- Given an interval vector and a mos, find its period complement. This is the | Given an interval vector and a mos, find its period complement. This is the | ||
interval to add to produce the period. | |||
]]-- | |||
function p.period_complement(interval, mos) | function p.period_complement(interval, mos) | ||
local sign = p.interval_step_count(interval) < 0 and -1 or 1 | local sign = p.interval_step_count(interval) < 0 and -1 or 1 | ||
| Line 602: | Line 664: | ||
end | end | ||
-- Given an interval vector and a mos, find its equave complement. This is the | --[[ | ||
Given an interval vector and a mos, find its equave complement. This is the | |||
interval to add to produce the equave. | |||
]]-- | |||
function p.equave_complement(interval, mos) | function p.equave_complement(interval, mos) | ||
local sign = p.interval_step_count(interval) < 0 and -1 or 1 | local sign = p.interval_step_count(interval) < 0 and -1 or 1 | ||
| Line 610: | Line 674: | ||
end | end | ||
-- Given an interval vector and a mos, period-reduce it. This works like | --[[ | ||
Given an interval vector and a mos, period-reduce it. This works like | |||
modular arithmetic, so passing a negative interval returns a positive one. | |||
]]-- | |||
function p.period_reduce(interval, mos) | function p.period_reduce(interval, mos) | ||
local step_count = p.interval_step_count(interval) | local step_count = p.interval_step_count(interval) | ||
| Line 620: | Line 686: | ||
end | end | ||
-- Given an interval vector and a mos, equave-reduce it. This works like | --[[ | ||
Given an interval vector and a mos, equave-reduce it. This works like | |||
modular arithmetic, so passing a negative interval returns a positive one. | |||
]]-- | |||
function p.equave_reduce(interval, mos) | function p.equave_reduce(interval, mos) | ||
local step_count = p.interval_step_count(interval) | local step_count = p.interval_step_count(interval) | ||
| Line 635: | Line 703: | ||
end | end | ||
-- Intervals usually denote distances between two scale degrees and should be | --[[ | ||
Intervals usually denote distances between two scale degrees and should be | |||
positive values. Normalizing makes a negative interval positive again. | |||
]]-- | |||
function p.normalize_interval(interval) | function p.normalize_interval(interval) | ||
return p.interval_step_count(interval) < 0 and p.interval_mul(interval, -1) or interval | return p.interval_step_count(interval) < 0 and p.interval_mul(interval, -1) or interval | ||
| Line 644: | Line 714: | ||
---------------------------- EQUAL-TUNING FUNCTIONS ---------------------------- | ---------------------------- EQUAL-TUNING FUNCTIONS ---------------------------- | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
--[[ | |||
-- Given a mos and a step ratio, return an equal tuning (or equal division). | Given a mos and a step ratio, return an equal tuning (or equal division). | ||
The step ratio is entered as a 2-element array to allow non-simplified | |||
ratios to be entered. (The rational module isn't suitable since it simplifies | |||
ratios.) | |||
]]-- | |||
function p.mos_to_et(mos, step_ratio, suffix) | function p.mos_to_et(mos, step_ratio, suffix) | ||
local suffix = suffix or nil | local suffix = suffix or nil | ||
| Line 655: | Line 726: | ||
end | end | ||
-- Given a mos and a step ratio, return the number of et-steps for its bright | --[[ | ||
-- | Given a mos and a step ratio, return the number of et-steps for its bright | ||
generator. | |||
]]-- | |||
function p.bright_gen_to_et_steps(mos, step_ratio) | function p.bright_gen_to_et_steps(mos, step_ratio) | ||
return p.interval_to_et_steps(p.bright_gen(mos), step_ratio) | return p.interval_to_et_steps(p.bright_gen(mos), step_ratio) | ||
end | end | ||
-- Given a mos and a step ratio, return the number of et-steps for its dark | -- Given a mos and a step ratio, return the number of et-steps for its dark generator. | ||
function p.dark_gen_to_et_steps(mos, step_ratio) | function p.dark_gen_to_et_steps(mos, step_ratio) | ||
return p.interval_to_et_steps(p.dark_gen(mos), step_ratio) | return p.interval_to_et_steps(p.dark_gen(mos), step_ratio) | ||
| Line 677: | Line 749: | ||
end | end | ||
-- Given an interval vector and step ratio, compute the number of et-steps it | -- Given an interval vector and step ratio, compute the number of et-steps it corresponds to. | ||
function p.interval_to_et_steps(interval, step_ratio) | function p.interval_to_et_steps(interval, step_ratio) | ||
return interval["L"] * step_ratio[1] + interval["s"] * step_ratio[2] | return interval["L"] * step_ratio[1] + interval["s"] * step_ratio[2] | ||
| Line 687: | Line 758: | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
-- Given a mos, return its equal temperament suffix as a string (edo, edt, edf, | -- Given a mos, return its equal temperament suffix as a string (edo, edt, edf, or ed-p/q). | ||
function p.mos_to_et_suffix(mos) | function p.mos_to_et_suffix(mos) | ||
if rat.eq(mos.equave, rat.new(2)) then | if rat.eq(mos.equave, rat.new(2)) then | ||
| Line 701: | Line 771: | ||
end | end | ||
-- Given a mos and step ratio, return its equal temperament as a string | -- Given a mos and step ratio, return its equal temperament as a string "{steps}\{division}{suffix}". | ||
function p.mos_to_et_as_string(mos, step_ratio, suffix) | function p.mos_to_et_as_string(mos, step_ratio, suffix) | ||
local suffix = suffix or nil | local suffix = suffix or nil | ||
| Line 760: | Line 829: | ||
end | end | ||
-- Given a mos and a step ratio, return the number of cents for its period. | --[[ | ||
Given a mos and a step ratio, return the number of cents for its period. | |||
The period is the interval at which the step pattern repeats, so no step | |||
ratio is needed. | |||
]]-- | |||
function p.period_to_cents(mos) | function p.period_to_cents(mos) | ||
return rat.cents(mos.equave) / p.period_count(mos) | return rat.cents(mos.equave) / p.period_count(mos) | ||
end | end | ||
-- Given a mos and a step ratio, return the number of cents for its equave. | --[[ | ||
Given a mos and a step ratio, return the number of cents for its equave. | |||
The period is the interval at which the step pattern repeats, and the equave | |||
-- | is a multiple of that (at least for multi-period mosses), so no step ratio is | ||
needed. | |||
]]-- | |||
function p.equave_to_cents(mos) | function p.equave_to_cents(mos) | ||
return rat.cents(mos.equave) | return rat.cents(mos.equave) | ||
end | end | ||
-- Given an interval vector and step ratio, convert it to cents. This requires | -- Given an interval vector and step ratio, convert it to cents. This requires info about the mos itself. | ||
function p.interval_to_cents(interval, mos, step_ratio) | function p.interval_to_cents(interval, mos, step_ratio) | ||
local interval_steps = p.interval_to_et_steps(interval, step_ratio) | local interval_steps = p.interval_to_et_steps(interval, step_ratio) | ||
| Line 800: | Line 873: | ||
--return p.mos_to_et(p.new(5,2), {2,1}) | --return p.mos_to_et(p.new(5,2), {2,1}) | ||
end | end | ||
return p | return p | ||