Module:MOS: Difference between revisions

ArrowHead294 (talk | contribs)
m Comment formatting
Ganaram inukshuk (talk | contribs)
mNo edit summary
 
(32 intermediate revisions by 2 users not shown)
Line 1: Line 1:
--[[
-- This module follows [[User:Ganaram inukshuk/Provisional style guide for Lua]]
  Module for working with mosses in lua code; this serves as a "library" for
local et    = require("Module:ET")
   mos-related modules and thus does not have a corresponding template.
local rat   = require("Module:Rational")
 
local utils = require("Module:Utils")
  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 utils = require("Module:Utils")
local et = require("Module:ET")
local tip = require("Module:Template input parse")
local p = {}
local p = {}
--[[
  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.
]]--


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------------------------------- HELPER FUNCTIONS -------------------------------
----------------------------- MOS-CREATING FUNCTIONS ---------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


function p.find_item_in_table(table, item)
-- Create a new mos as a table containing the counts for large and small steps,
local item_found = false
-- plus the equave.
for i = 1, #table do
if table[i] == item then
item_found = true
break
end
end
return item_found
end
 
--------------------------------------------------------------------------------
-------------------------------- BASE FUNCTIONS --------------------------------
--------------------------------------------------------------------------------
 
-- Create a new mos. (Contains the number of large and small steps, and equave.)
function p.new(nL, ns, equave)
function p.new(nL, ns, equave)
local nL = nL or 5
local nL = nL or 5
Line 64: Line 20:
end
end


-- Parse a mos from its scalesig.
-- Parse a mos from its scalesig "xL ys<p/q>" or "xL ys (p/q-equivalent)".
-- If no equave "p/q" is provided, it's assumed to be 2/1-equivalent.
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].-(%d+)[Ss]%s*(.*)$")
nL = tonumber(nL)
nL = tonumber(nL)
ns = tonumber(ns)
ns = tonumber(ns)
Line 79: Line 36:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------------------------------- STRING FUNCTIONS -------------------------------
---------------------- VALIDATION AND CHECKING FUNCTIONS -----------------------
--------------------------------------------------------------------------------
 
-- Is the mos xL ys valid (x and y are greater than 0)?
function p.is_valid(mos)
return mos.nL > 0 and mos.ns > 0
end
 
-- Is the mos xL ys octave-equivalent?
function p.is_octave_equivalent(mos)
return rat.eq(mos.equave, rat.new(2))
end
 
-- Is the mos nL ns? (Root mos, with root in the sense of being the root of
-- the scale tree.)
function p.is_root_mos(mos)
return mos.nL == mos.ns
end
 
--------------------------------------------------------------------------------
---------------------------- STRING/LINK FUNCTIONS -----------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


--[[
-- Construct a string representation (scalesig) for a MOS structure.
  Construct a string representation (scalesig) for a MOS structure.
-- Scalesig is "xL ys <p/q>" for valid mosses, omitting <p/q> for 2/1 scales.
  Scalesig is "xL ys", or "xL ys<p/q>" for nonoctave scales.
-- Degenerate mosses (nL 0s or 0L ns) produce a string for its corresponding
  Option to use nbsp is provided using the second param; default is no nbsp
-- et (n-ed-p/q).
]]--
-- Option to use nbsp is provided using the second param; default is nbsp.
function p.as_string(mos, use_nbsp)
function p.as_string(mos, use_nbsp)
local use_nbsp = use_nbsp or true
if p.is_valid(mos) then
local suffix = ""
local use_nbsp = (use_nbsp == nil and true or use_nbsp)
if not rat.eq(mos.equave, 2) then
local suffix = ""
suffix = "⟨" .. rat.as_ratio(mos.equave):lower() .. "⟩"
if not rat.eq(mos.equave, 2) then
suffix = "⟨" .. rat.as_ratio(mos.equave):lower() .. "⟩"
end
return mos.nL .. "L" .. (use_nbsp and "&nbsp;" or " ") .. mos.ns .. "s" .. suffix
else
return math.max(mos.nL, mos.ns) .. p.et_suffix(mos)
end
end
return "" .. mos.nL .. "L" .. (use_nbsp and "&nbsp;" or " ") .. mos.ns .. "s" .. suffix
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.
 
-- Degenerate mosses (nL 0s or 0L ns) produce a string for its corresponding
  Scalesig is "xL ys", or "xL ys (p/q-equivalent)" for nonoctave scales.
-- et (n-ed-p/q).
 
-- Option to use nbsp is provided using the second param; default is nbsp.
  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
if p.is_valid(mos) then
local suffix = ""
local use_nbsp = (use_nbsp ~= nil and use_nbsp or true)
if not rat.eq(mos.equave, 2) then
local suffix = ""
suffix = (use_nbsp and "&nbsp;" or " ") .. string.format("(%s-equivalent)", rat.as_ratio(mos.equave):lower())
if not rat.eq(mos.equave, 2) then
suffix = (use_nbsp and "&nbsp;" or " ") .. string.format("(%s-equivalent)", rat.as_ratio(mos.equave):lower())
end
return mos.nL .. "L" .. (use_nbsp and "&nbsp;" or " ") .. mos.ns .. "s" .. suffix
else
return math.max(mos.nL, mos.ns) .. p.et_suffix(mos)
end
end
return "" .. mos.nL .. "L" .. (use_nbsp and "&nbsp;" or " ") .. mos.ns .. "s" .. suffix
end
end


--[[
-- Construct the link to a mos. If the mos is a degenerate (nL 0s) mos, then it
  Given an interval as a vector of L's and s's, produce a string "iL + js",
-- will link to the corresponding equal-division page n-ed-p/q and display the
  where i and j are the quantities for L and s.
-- link text as an ed, rather than a mos.
]]--
function p.as_link(mos)
local link = p.as_long_string(mos)
local text = p.as_string(mos)
if link == text then
return string.format("[[%s]]", link)
else
return string.format("[[%s|%s]]", link, text)
end
end
 
-- Construct the link to a mos, where the displayed text is the long string
-- instead. Degenerate mosses link to the corresponding equal-division page.
function p.as_long_link(mos)
local link = p.as_long_string(mos)
return string.format("[[%s]]", link)
end
 
-- 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 148: Line 148:
return L_string .. (interval["s"] > 0 and " + " or " - ") .. s_string
return L_string .. (interval["s"] > 0 and " + " or " - ") .. s_string
end
end
end
-- Return the equave by itself as a string.
function p.equave_as_string(mos)
return rat.as_ratio(mos.equave)
end
-- Return the equave enclosed in brackets.
function p.equave_as_enclosed_string(mos)
return "⟨" .. rat.as_ratio(mos.equave) .. "⟩"
end
end


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------- MOS RELATIVES FUNCTIONS ----------------------------
----------------------- MOS RELATIVE/OPERATION FUNCTIONS -----------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


-- Find the parent mos of a mos
-- Find the parent mos of a mos. May return invalid mosses (nL 0s), meant to
function p.parent_mos(mos)
-- represent equal divisions of the octave (or arbitrary equave).
function p.parent(mos)
return p.new(math.min(mos.nL, mos.ns), math.abs(mos.nL-mos.ns), mos.equave)
return p.new(math.min(mos.nL, mos.ns), math.abs(mos.nL-mos.ns), mos.equave)
end
end


-- Find the two child mosses of a mos; ordered as soft and hard
-- Find the root of a mos nxL nys as nL ns.
function p.child_mosses(mos)
function p.root(mos)
local num_periods = p.period_count(mos)
return p.new(num_periods, num_periods, mos.equave)
end
 
-- Find the two child mosses of a mos xL ys as (x+y)L xs and xL x+ys.
function p.children(mos)
return p.new(mos.nL+mos.ns, mos.nL, mos.equave), p.new(mos.nL, mos.nL+mos.ns, mos.equave)
return p.new(mos.nL+mos.ns, mos.nL, mos.equave), p.new(mos.nL, mos.nL+mos.ns, mos.equave)
end
end


-- Find the sister of a mos
-- Find the sister of a mos xL ys as yL xs.
function p.sister_mos(mos)
function p.sister(mos)
return p.new(mos.ns, mos.nL, mos.equave)
return p.new(mos.ns, mos.nL, mos.equave)
end
end


-- Is the mos valid? It should have positive-integer values for nL and ns.
-- Find the neutralized form of a mos. May return invalid mosses (nL 0s), meant
function p.is_valid_mos(mos)
-- to represent equal divisions of the octave (or arbitrary equave).
return mos.nL > 0 and mos.ns > 0
function p.neutralized(mos)
if mos.nL > mos.ns then
return p.new(mos.nL-mos.ns, 2*mos.ns, mos.equave)
else
return p.new(2*mos.nL, mos.ns-mos.nL, mos.equave)
end
end
 
-- Find the two interleaved mosses of a mos xL ys as (2x+y)L ys and xL (x+2y)s.
function p.interleaved(mos)
return p.new(mos.nL*2+mos.ns, mos.ns, mos.equave), p.new(mos.nL, mos.ns*2+mos.nL, mos.equave)
end
end


Line 177: Line 204:
------------------------------- 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
  Calculation is based on the definition of a Christoffel word, as the closest
-- integer approximation to line y = #s/#L*x.
  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 204: Line 229:
return string.rep(result, d)
return string.rep(result, d)
end
end


-- Find the darkest true-mos mode of a mos. It's the reverse of the brightest mode.
-- Find the darkest true-mos mode of a mos. It's the reverse of the brightest mode.
Line 230: Line 254:
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 DOWN PER PERIOD:
 
-- 0 is the brightest mode, 1 is 2nd brightest, etc...
  Ordering here is based on the number of bright gens going DOWN PER PERIOD:
-- To go by darkness, pass in p-d-1 for the 2nd arg, where p is the period count
  0 is the brightest mode, 1 is 2nd brightest, etc...
-- and d is the number of DARK GENS UP PER PERIOD.
]]--
function p.mode_by_brightness(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))
end
end
--------------------------------------------------------------------------------
--------------------------- MODE ROTATION FUNCTIONS ----------------------------
--------------------------------------------------------------------------------


-- Given a mos, list all modes in descending order of brightness.
-- Given a mos, list all modes in descending order of brightness.
Line 259: Line 278:
end
end


--[[
-- List all unique rotations for a mode, by order of leftward shifts. Order by
  List all unique rotations for a mode. Order of modes is by rotation.
-- rotation will usually give a different order compared to order by brightness,
 
-- but this is expected if the order isn't by brightness (EG, modmosses).
  Note: there will always be s/p modes, where s is the number of steps in the
-- 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
-- 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
-- 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
-- (where s mod p = 0), then there will be p modes. If the mode has one step
  possible to have only one mode, but this can only happen if there is only one
-- type, then there is only one mode.
  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 = {}
local current_mode = mode_string
local current_mode = mode_string
for i = 1, #mode_string do
for i = 1, #mode_string do
if not p.find_item_in_table(rotations, current_mode) then
if not utils.table_contains(rotations, current_mode) then
table.insert(rotations, current_mode)
table.insert(rotations, current_mode)
end
end
Line 281: Line 298:
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_by_brightness().
  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 295: Line 310:
---------------------------- 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.
-- 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 308: Line 322:
end
end


--[[
 
  Given a mos, produce every step matrix for every mode. Modes are listed in
-- TODO?: replaces mode_to_step_matrices/mode_rotations_to_step_matrices with
  order of brightness.
-- one function called modes_to_step_matrices? Encompasses functionality of both
]]--
-- functions, but step patterns for either are generated into the same function,
-- where the modes as strings are passed 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 322: Line 341:
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.
  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 336: Line 353:
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.
 
  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 366: Line 380:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------- INTERVAL FUNCTIONS FOR PERFECTABLE INTERVALS -------------------
--------------- FUNCTIONS FOR GENERATOR 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. Since all mosstep
 
-- intervals (excluding the root and period) have two sizes, this returns the
  Bright gen has two sizes: perfect (large) and diminished (small). The size
-- large/perfect 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 405: Line 416:
end
end


--[[
-- Compute the dark gen as a vector of L's and s's. Since all mosstep
  Compute the dark gen as a vector of L's and s's.
-- intervals (excluding the root and period) have two sizes, this returns the
 
-- small/perfect size.
  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 417: Line 424:
end
end


-- Compute the period as a vector of L's and s's. Period intervals only have one size: perfect.
-- Compute the period as a vector of L's and s's.
-- Period intervals as mossteps only appear as one size.
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 426: Line 434:
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.
-- Equaves as mossteps only appear as one size. For a single-period mos, this
 
-- is the same as p.period().
  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 440: Line 445:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
------------------ INTERVAL FUNCTIONS FOR SIMPLE INTERVALS ---------------------
------------------- FUNCTIONS FOR SINGLE-STEP INTERVALS ------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--[[
 
  Compute the unison as a vector of L's and s's.
-- Return 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
  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.
  not need a mos as input. It's basically a zero vector.
-- The unison only has one size: perfect.
 
  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


--[[
-- Return 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".
  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 }
end
end


-- Compute the vector for an augmented step. It's a large step plus a chroma.
-- Return the vector for an augmented step. It's a large step plus a chroma.
function p.augmented_step()
function p.augmented_step()
return { ["L"] = 2, ["s"] = -1 }
return { ["L"] = 2, ["s"] = -1 }
end
end


-- Compute the vector for a single large step.
-- Return the vector for a single large step.
function p.large_step()
function p.large_step()
return { ["L"] = 1, ["s"] = 0 }
return { ["L"] = 1, ["s"] = 0 }
end
end


-- Compute the vector for a single small step.
-- Return the vector for a single small step.
function p.small_step()
function p.small_step()
return { ["L"] = 0, ["s"] = 1 }
return { ["L"] = 0, ["s"] = 1 }
end
end


-- Compute the vector for a diminished step. It's a small step minus a chroma.
-- Return the vector for a diminished step. It's a small step minus a chroma.
function p.diminished_step()
function p.diminished_step()
return { ["L"] = -1, ["s"] = 2 }
return { ["L"] = -1, ["s"] = 2 }
Line 491: Line 491:
end
end


--[[
-- Compute an arbitrary mos interval as a vector of L's and s's. Params:
  Compute an arbitrary mos interval as a vector of L's and s's.
-- - step_count: the number of steps subtended by the mosstep.
 
-- - size_offset: denotes whether to return the large size (0) or the small
  The step_count param is the number of mossteps in the interval. EG, in 5L 2s,
--   size (-1) (or if this is a period interval, the diminished size). Values
  the large 2-mosstep is "LL", so the corresponding vector has L=2, s=0.
--   other than 0 or 1 represent alterations by multiple chromas, such as
 
--   augmented (1) or diminished (-2).
  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 523: Line 510:
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().
  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.
  Sequences of steps can be entered, where each step is one of five sizes:
--  - s: small step.
  - L: large step.
--  - c: a chroma; the difference between a large and small step.
  - s: small step.
--  - A: an augmented step; a large step plus a chroma.
  - c: a chroma; the difference between a large and small step.
--  - d: a diminished step, or diesis; a small step minus a chroma.
  - 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 559: Line 543:
------------------------------- COUNT FUNCTIONS --------------------------------
------------------------------- COUNT FUNCTIONS --------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Given a mos, return the number of steps.
function p.step_count(mos)
return mos.nL + mos.ns
end


-- Given a mos, compute the number of steps in its bright gen (L's plus s's).
-- Given a mos, compute the number of steps in its bright gen (L's plus s's).
Line 576: Line 565:
end
end


-- Given a mos, compute the number of steps in its equave (L's plus s's).
-- TODO: deprecate this since "equave_step_count" is redundant and longer than
-- "step count".
function p.equave_step_count(mos)
function p.equave_step_count(mos)
return mos.nL + mos.ns
return mos.nL + mos.ns
Line 586: Line 576:
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
  corresponds to. Knowledge of the corresponding mos is not needed. Intervals
-- can be negative, resulting in a negative output.
  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
  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
  perfect size (for period/root/equave intervals). This requires the mos as
-- input.
  input.
-- size_offset denotes whether to count chromas from the large size; changing
 
-- this to -1 counts chromas from the small size. Like size_offset for
  If the number of chromas from a small (EG minor) interval is desired, then
-- interval_from_mos, this can be used to denote altered mossteps (augmented,
  using the param size_offset can be used: 0 for chromas from large size, -1
-- diminished, etc).
  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 618: Line 600:


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
----------------------- INTERVAL ARITHMETIC FUNCTIONS --------------------------
--------------- INTERVAL ARITHMETIC AND MANIPULATION FUNCTIONS -----------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------


Line 637: Line 619:
end
end


-- Repeatedly add the same interval to itself.
-- Stack an interval, or repeatedly add the same interval to itself.
function p.interval_mul(interval, amt)
function p.interval_mul(interval, amt)
return {  
return {  
Line 651: Line 633:
interval_1["s"] == interval_2["s"]
interval_1["s"] == interval_2["s"]
end
end
--------------------------------------------------------------------------------
 
---------------------- INTERVAL MANIPULATION FUNCTIONS -------------------------
-- Given an interval vector and a mos, find its period complement. This is the
--------------------------------------------------------------------------------
-- interval to add to produce the period. For single-period mosses, the period
--[[
-- complement is the same as the equave complement.
  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 664: Line 643:
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.
  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 674: Line 651:
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.
  modular arithmetic, so passing a negative interval returns a positive one.
-- For single-period mosses, period-reducing is the same as octave-reducing, or
]]--
-- equave-reducing (for nonoctave scales).
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 686: Line 663:
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.
  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 703: Line 678:
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.
  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 714: Line 687:
---------------------------- 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
-- 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 to be entered. (The rational module isn't suitable since it simplifies
  ratios.)
-- ratios.)
]]--
function p.as_et(mos, step_ratio, suffix)
function p.mos_to_et(mos, step_ratio, suffix)
local suffix = suffix or nil
local suffix = suffix or nil
local et_size = mos.nL * step_ratio[1] + mos.ns * step_ratio[2]
local et_size = mos.nL * step_ratio[1] + mos.ns * step_ratio[2]
Line 726: Line 698:
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.
  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)
Line 759: Line 729:


-- Given a mos, return its equal temperament suffix as a string (edo, edt, edf, or ed-p/q).
-- 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.et_suffix(mos)
if rat.eq(mos.equave, rat.new(2)) then
if rat.eq(mos.equave, rat.new(2)) then
return "edo"
return "edo"
Line 772: Line 742:


-- Given a mos and step ratio, return its equal temperament as a string "{steps}\{division}{suffix}".
-- 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.et_string(mos, step_ratio, suffix)
local suffix = suffix or nil
local suffix = suffix or nil
local et_mos = p.mos_to_et(mos, step_ratio, suffix)
local et_mos = p.as_et(mos, step_ratio, suffix)
return et.as_string(et_mos)
return et.as_string(et_mos)
end
end
Line 780: Line 750:
-- Given a mos and step ratio, compute the number of et-steps for its bright gen
-- Given a mos and step ratio, compute the number of et-steps for its bright gen
-- as a string "{steps}\{division}{suffix}".
-- as a string "{steps}\{division}{suffix}".
function p.bright_gen_to_et_steps_as_string(mos, step_ratio, suffix)
function p.bright_gen_to_et_string(mos, step_ratio, suffix)
return p.interval_to_et_steps_as_string(p.bright_gen(mos), mos, step_ratio, suffix)
return p.interval_to_et_string(p.bright_gen(mos), mos, step_ratio, suffix)
end
end


-- Given a mos and step ratio, compute the number of et-steps for its dark gen,
-- Given a mos and step ratio, compute the number of et-steps for its dark gen,
-- as a string "{steps}\{division}{suffix}".
-- as a string "{steps}\{division}{suffix}".
function p.dark_gen_to_et_steps_as_string(mos, step_ratio, suffix)
function p.dark_gen_to_et_string(mos, step_ratio, suffix)
return p.interval_to_et_steps_as_string(p.dark_gen(mos), mos, step_ratio, suffix)
return p.interval_to_et_string(p.dark_gen(mos), mos, step_ratio, suffix)
end
end


-- Given a mos and step ratio, compute the number of et-steps for its period,
-- Given a mos and step ratio, compute the number of et-steps for its period,
-- as a string "{steps}\{division}{suffix}".
-- as a string "{steps}\{division}{suffix}".
function p.period_to_et_steps_as_string(mos, step_ratio, suffix)
function p.period_to_et_string(mos, step_ratio, suffix)
return p.interval_to_et_steps_as_string(p.period(mos), mos, step_ratio, suffix)
return p.interval_to_et_string(p.period(mos), mos, step_ratio, suffix)
end
 
-- Given a mos, compute the number of et-steps for its period, reduced,
-- as a string "{steps}\{division}{suffix}". Does not reuqire a step ratio.
-- NOTE: no such function for returning only the number of steps is needed since
-- that's the same as period_count().
function p.reduced_period_to_et_string(mos, suffix)
return p.interval_to_et_string({["L"] = 1, ["s"] = 1}, p.root(mos), {1,0}, suffix)
end
end


-- Given a mos and step ratio, compute the number of et-steps for its equave,
-- Given a mos and step ratio, compute the number of et-steps for its equave,
-- as a string "{steps}\{division}{suffix}".
-- as a string "{steps}\{division}{suffix}".
function p.equave_to_et_steps_as_string(mos, step_ratio, suffix)
function p.equave_to_et_string(mos, step_ratio, suffix)
return p.interval_to_et_steps_as_string(p.equave(mos), mos, step_ratio, suffix)
return p.interval_to_et_string(p.equave(mos), mos, step_ratio, suffix)
end
end


Line 805: Line 783:
-- corresponds to, as a string "{steps}\{division}{suffix}". Requires info
-- corresponds to, as a string "{steps}\{division}{suffix}". Requires info
-- about the mos itself.
-- about the mos itself.
function p.interval_to_et_steps_as_string(interval, mos, step_ratio, suffix)
function p.interval_to_et_string(interval, mos, step_ratio, suffix)
local suffix = suffix or nil
local suffix = suffix or nil
local mos_et = p.mos_to_et(mos, step_ratio, suffix)
local mos_et = p.as_et(mos, step_ratio, suffix)
return et.backslash_display(mos_et, p.interval_to_et_steps(interval, step_ratio))
return et.backslash_display(mos_et, p.interval_to_et_steps(interval, step_ratio))
end
end
Line 829: Line 807:
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.
  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
  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
  is a multiple of that (at least for multi-period mosses), so no step ratio is
-- needed.
  needed.
]]--
function p.equave_to_cents(mos)
function p.equave_to_cents(mos)
return rat.cents(mos.equave)
return rat.cents(mos.equave)
Line 862: Line 835:
-- Tester function
-- Tester function
function p.tester()
function p.tester()
local input_mos = p.new(5,2)
local input_mos = p.new(4,1,3)
local step_ratio = {2,1}
local step_ratio = {2,1}
local interval_vector = {["L"] = 3, ["s"] = 1}
local interval_vector = {["L"] = 3, ["s"] = 1}
return p.mos_to_et_as_string(input_mos, step_ratio) .. "\n"
--return p.as_string(input_mos, false)
.. p.bright_gen_to_et_steps_as_string(input_mos, step_ratio) .. "\n"
.. p.dark_gen_to_et_steps_as_string(input_mos, step_ratio) .. "\n"
.. p.equave_to_et_steps_as_string(input_mos, step_ratio) .. "\n"
.. p.period_to_et_steps_as_string(input_mos, step_ratio) .. "\n"
.. p.interval_to_et_steps_as_string(interval_vector, input_mos, step_ratio)


--return p.mos_to_et(p.new(5,2), {2,1})
--return p.as_et(p.new(5,2), {2,1})
--[[
return
p.mode_by_brightness(p.new(5,2), 0) .. " " .. p.mode_by_brightness(p.new(5,2), 6-6) .. "\n" ..
p.mode_by_brightness(p.new(5,2), 1) .. " " .. p.mode_by_brightness(p.new(5,2), 6-5) .. "\n" ..
p.mode_by_brightness(p.new(5,2), 2) .. " " .. p.mode_by_brightness(p.new(5,2), 6-4) .. "\n" ..
p.mode_by_brightness(p.new(5,2), 3) .. " " .. p.mode_by_brightness(p.new(5,2), 6-3) .. "\n" ..
p.mode_by_brightness(p.new(5,2), 4) .. " " .. p.mode_by_brightness(p.new(5,2), 6-2) .. "\n" ..
p.mode_by_brightness(p.new(5,2), 5) .. " " .. p.mode_by_brightness(p.new(5,2), 6-1) .. "\n" ..
p.mode_by_brightness(p.new(5,2), 6) .. " " .. p.mode_by_brightness(p.new(5,2), 6-0)
]]--
return
p.as_string(p.new(5,2))        .. "\n" ..
p.as_string(p.new(4,5,3))      .. "\n" ..
p.as_long_string(p.new(5,2))  .. "\n" ..
p.as_long_string(p.new(4,5,3)) .. "\n" ..
p.as_link(p.new(5,2))          .. "\n" ..
p.as_link(p.new(4,5,3))        .. "\n" ..
p.as_long_link(p.new(5,2))    .. "\n" ..
p.as_long_link(p.new(4,5,3))  .. "\n" ..
p.as_string(p.new(5,0))        .. "\n" ..
p.as_string(p.new(4,0,3))      .. "\n" ..
p.as_long_string(p.new(5,0))  .. "\n" ..
p.as_long_string(p.new(4,0,3)) .. "\n" ..
p.as_link(p.new(5,0))          .. "\n" ..
p.as_link(p.new(4,0,3))        .. "\n" ..
p.as_long_link(p.new(5,0))    .. "\n" ..
p.as_long_link(p.new(4,0,3))  .. "\n" ..
p.as_string(p.new(0,2))        .. "\n" ..
p.as_string(p.new(0,5,3))      .. "\n" ..
p.as_long_string(p.new(0,2))  .. "\n" ..
p.as_long_string(p.new(0,5,3)) .. "\n" ..
p.as_link(p.new(0,2))          .. "\n" ..
p.as_link(p.new(0,5,3))        .. "\n" ..
p.as_long_link(p.new(0,2))    .. "\n" ..
p.as_long_link(p.new(0,5,3))
end
end


return p
return p